From 1a5ad278748d09f0ab64beb8e0b2bae769e7bf43 Mon Sep 17 00:00:00 2001 From: frikky Date: Wed, 22 Feb 2023 23:03:57 +0100 Subject: [PATCH 001/259] Started fixing subflow to also handle user input to be used onprem & in cloud --- shuffle-subflow/1.0.0/api.yaml | 41 ++++++++++++++++++ shuffle-subflow/1.0.0/src/app.py | 74 +++++++++++++++++++++++++++++--- shuffle-tools/1.2.0/api.yaml | 35 +++++++++++++++ shuffle-tools/1.2.0/src/app.py | 27 ++++++++++-- 4 files changed, 166 insertions(+), 11 deletions(-) diff --git a/shuffle-subflow/1.0.0/api.yaml b/shuffle-subflow/1.0.0/api.yaml index 9d2642bb..4148c19a 100644 --- a/shuffle-subflow/1.0.0/api.yaml +++ b/shuffle-subflow/1.0.0/api.yaml @@ -58,4 +58,45 @@ actions: returns: schema: type: string + - name: run_userinput + description: Stops a workflow and notifies the right people + parameters: + - name: user_apikey + description: The apikey to connect back to the APIs + required: true + multiline: false + example: "apikey" + schema: + type: string + - name: sms + description: The numbers to send an sms to + required: false + multiline: false + example: "+474135212,+180241322" + schema: + type: string + - name: email + description: The emails to send an email to + required: false + multiline: false + example: "example@shuffler.io,test@test.com" + schema: + type: string + - name: subflow + description: The subflow IDs to start + required: false + multiline: false + example: "7944b41d-6200-4f28-8973-22ba52637bf0,4832b41d-6200-4f28-8973-22ba52637bf0" + schema: + type: string + - name: information + description: The information to send to the targets + required: false + multiline: true + example: "This is an argument using some liquid: {{ 1 + 2 }} " + schema: + type: string + returns: + schema: + type: string large_image: data:image/png;base64,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 diff --git a/shuffle-subflow/1.0.0/src/app.py b/shuffle-subflow/1.0.0/src/app.py index 6abdd21f..c6b1da4e 100644 --- a/shuffle-subflow/1.0.0/src/app.py +++ b/shuffle-subflow/1.0.0/src/app.py @@ -25,18 +25,78 @@ def __init__(self, redis, logger, console_logger=None): super().__init__(redis, logger, console_logger) # Should run user input - #def run_userinput(self, sms, email, subflow, argument): - # url = "%s/api/v1/workflows/%s/execute" % (self.url, workflow) + def run_userinput(self, user_apikey, sms="", email="", subflow="", information="", startnode="", backend_url=""): + #url = "%s/api/v1/workflows/%s/execute" % (self.url, workflow) - # if len(sms) > 0: + headers = { + "Authorization": "Bearer %s" % user_apikey, + "User-Agent": "Shuffle Userinput 1.0.0" + } + + result = { + "success": True, + "source": "userinput", + "reason": "Userinput data sent and workflow paused. Waiting for user input before continuing workflow." + } + + url = self.url + if len(str(backend_url)) > 0: + url = "%s" % (backend_url) + + if len(email): + jsondata = { + "targets": [], + "body": information, + "subject": "User input required", + "type": "User input", + "start": startnode, + "workflow_id": self.full_execution["workflow"]["id"], + "reference_execution": self.full_execution["execution_id"], + } + + for item in email.split(","): + jsondata["targets"].append(item.strip()) + + print("Should run email with targets: %s", jsondata["targets"]) + + ret = requests.post("%s/api/v1/functions/sendmail" % url, json=jsondata, headers=headers) + if ret.status_code != 200: + print("Failed sending email. Data: %s" % ret.text) + + if len(sms) > 0: + print("Should run SMS: %s", sms) + + jsondata = { + "numbers": [], + "body": information, + "type": "User input", + "start": startnode, + "workflow_id": self.full_execution["workflow"]["id"], + "reference_execution": self.full_execution["execution_id"], + } + + for item in sms.split(","): + jsondata["numbers"].append(item.strip()) + + print("Should send sms with targets: %s", jsondata["numbers"]) + + ret = requests.post("%s/api/v1/functions/sendsms" % url, json=jsondata, headers=headers) + if ret.status_code != 200: + print("Failed sending email. Data: %s" % ret.text) + + if len(subflow): + print("Should run subflow: %s", subflow) + + if len(information): + print("Should run arg: %s", information) + + return json.dumps(result) def run_subflow(self, user_apikey, workflow, argument, source_workflow="", source_execution="", source_node="", source_auth="", startnode="", backend_url=""): #print("STARTNODE: %s" % startnode) url = "%s/api/v1/workflows/%s/execute" % (self.url, workflow) - params = { - "User-Agent": "Subflow 1.0.0" - } + params = {} if len(str(source_workflow)) > 0: params["source_workflow"] = source_workflow else: @@ -66,9 +126,9 @@ def run_subflow(self, user_apikey, workflow, argument, source_workflow="", sourc url = "%s/api/v1/workflows/%s/execute" % (backend_url, workflow) print("[INFO] Changed URL to %s for this execution" % url) - headers = { "Authorization": "Bearer %s" % user_apikey, + "User-Agent": "Shuffle Subflow 1.0.0" } if len(str(argument)) == 0: diff --git a/shuffle-tools/1.2.0/api.yaml b/shuffle-tools/1.2.0/api.yaml index 576b41ab..3c3a4bbd 100644 --- a/shuffle-tools/1.2.0/api.yaml +++ b/shuffle-tools/1.2.0/api.yaml @@ -747,6 +747,41 @@ actions: example: "json_key" schema: type: string + - name: merge_json_objects + description: Merges two lists of same type AND length. + parameters: + - name: list_one + description: The first list + multiline: true + example: "{'key': 'value'}" + required: true + schema: + type: string + - name: list_two + description: The second list to use + multiline: true + required: true + example: "{'key2': 'value2'}" + schema: + type: string + - name: set_field + description: If items in list 2 are strings, but first is JSON, sets the values to the specified key. Defaults to key "new_shuffle_key" + required: false + example: "json_key" + schema: + type: string + - name: sort_key_list_one + description: Sort by this key before using list one for merging + required: false + example: "json_key" + schema: + type: string + - name: sort_key_list_two + description: Sort by this key before using list two for merging + required: false + example: "json_key" + schema: + type: string - name: diff_lists description: Diffs two lists of strings or integers and finds what's missing parameters: diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index fc4e5c90..ae32fb35 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -1133,6 +1133,7 @@ def extract_archive(self, file_id, fileformat="zip", password=None): filename = os.path.basename(member) if not filename: continue + source = z_file.open(member) to_be_uploaded.append( {"filename": source.name, "data": source.read()} @@ -1156,6 +1157,10 @@ def extract_archive(self, file_id, fileformat="zip", password=None): ) as z_file: for member in z_file.getnames(): member_files = z_file.extractfile(member) + + if not member_files: + continue + to_be_uploaded.append( { "filename": member, @@ -1174,18 +1179,22 @@ def extract_archive(self, file_id, fileformat="zip", password=None): ) elif fileformat.strip().lower() == "tar.gz": try: - with tarfile.open( - os.path.join(tmpdirname, "archive"), mode="r:gz" - ) as z_file: + with tarfile.open(os.path.join(tmpdirname, "archive"), mode="r:gz") as z_file: for member in z_file.getnames(): member_files = z_file.extractfile(member) + + if not member_files: + continue + to_be_uploaded.append( { "filename": member, "data": member_files.read(), } ) + return_data["success"] = True + except Exception as e: return_data["files"].append( { @@ -1436,6 +1445,7 @@ def diff(li1, li2): "diff": newdiff, } + def merge_lists(self, list_one, list_two, set_field="", sort_key_list_one="", sort_key_list_two=""): if isinstance(list_one, str): try: @@ -1450,7 +1460,13 @@ def merge_lists(self, list_one, list_two, set_field="", sort_key_list_one="", so self.logger.info("Failed to parse list2 as json: %s" % e) if not isinstance(list_one, list) or not isinstance(list_two, list): - return {"success": False, "message": "Input lists need to be valid JSON lists."} + if isinstance(list_one, dict) and isinstance(list_two, dict): + for key, value in list_two.items(): + list_one[key] = value + + return list_one + + return {"success": False, "message": "Both input lists need to be valid JSON lists."} if len(list_one) != len(list_two): return {"success": False, "message": "Lists length must be the same. %d vs %d" % (len(list_one), len(list_two))} @@ -1499,6 +1515,9 @@ def merge_lists(self, list_one, list_two, set_field="", sort_key_list_one="", so return list_one + def merge_json_objects(self, list_one, list_two, set_field="", sort_key_list_one="", sort_key_list_two=""): + self.merge_lists(self, list_one, list_two, set_field="", sort_key_list_one="", sort_key_list_two="") + def fix_json(self, json_data): try: deletekeys = [] From bd443aaecbe0f5aae555810d1b0682dc2a14ce76 Mon Sep 17 00:00:00 2001 From: frikky Date: Sun, 26 Feb 2023 16:46:42 +0100 Subject: [PATCH 002/259] Fixed issue with shuffle tools filter list. Now supports wildcard * as well --- http/1.3.0/api.yaml | 14 +-- shuffle-subflow/1.0.0/src/app.py | 6 + shuffle-tools/1.2.0/src/app.py | 181 ++++++++----------------------- 3 files changed, 60 insertions(+), 141 deletions(-) diff --git a/http/1.3.0/api.yaml b/http/1.3.0/api.yaml index a90ed131..e30294a7 100644 --- a/http/1.3.0/api.yaml +++ b/http/1.3.0/api.yaml @@ -27,7 +27,7 @@ actions: description: Headers to use multiline: true required: false - example: "Authorization: Bearer asd\nContent-Type: application/json" + example: "Content-Type: application/json" schema: type: string - name: username @@ -110,7 +110,7 @@ actions: description: Headers to use multiline: true required: false - example: "Authorization: Bearer asd\nContent-Type: application/json" + example: "Content-Type: application/json" schema: type: string - name: username @@ -183,7 +183,7 @@ actions: description: Headers to use multiline: true required: false - example: "Authorization: Bearer asd\nContent-Type: application/json" + example: "Content-Type: application/json" schema: type: string - name: username @@ -256,7 +256,7 @@ actions: description: Headers to use multiline: true required: false - example: "Authorization: Bearer asd\nContent-Type: application/json" + example: "Content-Type: application/json" schema: type: string - name: username @@ -322,7 +322,7 @@ actions: description: Headers to use multiline: true required: false - example: "Authorization: Bearer asd\nContent-Type: application/json" + example: "Content-Type: application/json" schema: type: string - name: username @@ -388,7 +388,7 @@ actions: description: Headers to use multiline: true required: false - example: "Authorization: Bearer asd\nContent-Type: application/json" + example: "Content-Type: application/json" schema: type: string - name: username @@ -454,7 +454,7 @@ actions: description: Headers to use multiline: true required: false - example: "Authorization: Bearer asd\nContent-Type: application/json" + example: "Content-Type: application/json" schema: type: string - name: username diff --git a/shuffle-subflow/1.0.0/src/app.py b/shuffle-subflow/1.0.0/src/app.py index c6b1da4e..08d5191a 100644 --- a/shuffle-subflow/1.0.0/src/app.py +++ b/shuffle-subflow/1.0.0/src/app.py @@ -62,6 +62,9 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" ret = requests.post("%s/api/v1/functions/sendmail" % url, json=jsondata, headers=headers) if ret.status_code != 200: print("Failed sending email. Data: %s" % ret.text) + result["email"] = False + else: + result["email"] = True if len(sms) > 0: print("Should run SMS: %s", sms) @@ -83,6 +86,9 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" ret = requests.post("%s/api/v1/functions/sendsms" % url, json=jsondata, headers=headers) if ret.status_code != 200: print("Failed sending email. Data: %s" % ret.text) + result["sms"] = False + else: + result["sms"] = True if len(subflow): print("Should run subflow: %s", subflow) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index ae32fb35..54ca59ba 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -102,6 +102,9 @@ def base64_conversion(self, string, operation): }) def parse_list_internal(self, input_list): + if isinstance(input_list, list): + input_list = ",".join(input_list) + try: input_list = json.loads(input_list) if isinstance(input_list, list): @@ -564,6 +567,20 @@ def execute_bash(self, code, shuffle_input): return item + # Check if wildcardstring is in all_ips and support * as wildcard + def check_wildcard(self, wildcardstring, matching_string): + wildcardstring = str(wildcardstring.lower()) + if wildcardstring in str(matching_string).lower(): + return True + else: + wildcardstring = wildcardstring.replace(".", "\.") + wildcardstring = wildcardstring.replace("*", ".*") + + if re.match(wildcardstring, str(matching_string).lower()): + return True + + return False + def filter_list(self, input_list, field, check, value, opposite): self.logger.info(f"\nRunning function with list {input_list}") @@ -574,7 +591,6 @@ def filter_list(self, input_list, field, check, value, opposite): if str(opposite).lower() == "true": flip = True - try: #input_list = eval(input_list) # nosec input_list = json.loads(input_list) @@ -632,15 +648,9 @@ def filter_list(self, input_list, field, check, value, opposite): if str(tmp).lower() == str(value).lower(): self.logger.info("APPENDED BECAUSE %s %s %s" % (field, check, value)) - if not flip: - new_list.append(item) - else: - failed_list.append(item) + new_list.append(item) else: - if flip: - new_list.append(item) - else: - failed_list.append(item) + failed_list.append(item) elif check == "equals any of": self.logger.info("Inside equals any of") @@ -667,103 +677,52 @@ def filter_list(self, input_list, field, check, value, opposite): # IS EMPTY FOR STR OR LISTS elif check == "is empty": - if tmp == "[]": + if str(tmp) == "[]": tmp = [] - if type(tmp) == list and len(tmp) == 0 and not flip: - new_list.append(item) - elif type(tmp) == list and len(tmp) > 0 and flip: - new_list.append(item) - elif type(tmp) == str and not tmp and not flip: + if str(tmp) == "{}": + tmp = [] + + if type(tmp) == list and len(tmp) == 0: new_list.append(item) - elif type(tmp) == str and tmp and flip: + elif type(tmp) == str and not tmp: new_list.append(item) else: failed_list.append(item) # STARTS WITH = FOR STR OR [0] FOR LIST elif check == "starts with": - if type(tmp) == list and tmp[0] == value and not flip: + if type(tmp) == list and tmp[0] == value: new_list.append(item) - elif type(tmp) == list and tmp[0] != value and flip: - new_list.append(item) - elif type(tmp) == str and tmp.startswith(value) and not flip: - new_list.append(item) - elif type(tmp) == str and not tmp.startswith(value) and flip: + elif type(tmp) == str and tmp.startswith(value): new_list.append(item) else: failed_list.append(item) # ENDS WITH = FOR STR OR [-1] FOR LIST elif check == "ends with": - if type(tmp) == list and tmp[-1] == value and not flip: - new_list.append(item) - elif type(tmp) == list and tmp[-1] != value and flip: + if type(tmp) == list and tmp[-1] == value: new_list.append(item) - elif type(tmp) == str and tmp.endswith(value) and not flip: - new_list.append(item) - elif type(tmp) == str and not tmp.endswith(value) and flip: + elif type(tmp) == str and tmp.endswith(value): new_list.append(item) else: failed_list.append(item) # CONTAINS FIND FOR LIST AND IN FOR STR elif check == "contains": - if type(tmp) == list and value.lower() in tmp and not flip: - new_list.append(item) - elif type(tmp) == list and value.lower() not in tmp and flip: - new_list.append(item) - elif ( - type(tmp) == str - and tmp.lower().find(value.lower()) != -1 - and not flip - ): - new_list.append(item) - elif ( - type(tmp) == str - and tmp.lower().find(value.lower()) == -1 - and flip - ): + #if str(value).lower() in str(tmp).lower(): + if str(value).lower() in str(tmp).lower() or self.check_wildcard(value, tmp): new_list.append(item) else: failed_list.append(item) + elif check == "contains any of": - self.logger.info("Inside contains any of") + value = self.parse_list_internal(value) checklist = value.split(",") - self.logger.info("Checklist and tmp: %s - %s" % (checklist, tmp)) + self.logger.info("CHECKLIST: %s. Value: %s" % (checklist, tmp)) found = False - for subcheck in checklist: - subcheck = subcheck.strip().lower() - #ext.lower().strip() == value.lower().strip() - if type(tmp) == list and subcheck in tmp and not flip: - new_list.append(item) - found = True - break - elif type(tmp) == list and subcheck in tmp and flip: - failed_list.append(item) - found = True - break - elif type(tmp) == list and subcheck not in tmp and not flip: - new_list.append(item) - found = True - break - elif type(tmp) == list and subcheck not in tmp and flip: - failed_list.append(item) - found = True - break - elif (type(tmp) == str and tmp.lower().find(subcheck) != -1 and not flip): - new_list.append(item) - found = True - break - elif (type(tmp) == str and tmp.lower().find(subcheck) != -1 and flip): - failed_list.append(item) - found = True - break - elif (type(tmp) == str and tmp.lower().find(subcheck) == -1 and not flip): - failed_list.append(item) - found = True - break - elif (type(tmp) == str and tmp.lower().find(subcheck) == -1 and flip): + for checker in checklist: + if str(checker).lower() in str(tmp).lower() or self.check_wildcard(checker, tmp): new_list.append(item) found = True break @@ -774,58 +733,20 @@ def filter_list(self, input_list, field, check, value, opposite): # CONTAINS FIND FOR LIST AND IN FOR STR elif check == "field is unique": #self.logger.info("FOUND: %s" - if tmp.lower() not in found_items and not flip: - new_list.append(item) - found_items.append(tmp.lower()) - elif tmp.lower() in found_items and flip: + if tmp.lower() not in found_items: new_list.append(item) found_items.append(tmp.lower()) else: failed_list.append(item) - #tmp = json.dumps(tmp) - - #for item in new_list: - #if type(tmp) == list and value.lower() in tmp and not flip: - # new_list.append(item) - # found = True - # break - #elif type(tmp) == list and value.lower() not in tmp and flip: - # new_list.append(item) - # found = True - # break - # CONTAINS FIND FOR LIST AND IN FOR STR - elif check == "contains any of": - value = self.parse_list_internal(value) - checklist = value.split(",") - tmp = tmp - self.logger.info("CHECKLIST: %s. Value: %s" % (checklist, tmp)) - found = False - for value in checklist: - if value in tmp and not flip: - new_list.append(item) - found = True - break - elif value not in tmp and flip: - new_list.append(item) - found = True - break - - if not found: - failed_list.append(item) - elif check == "larger than": - if int(tmp) > int(value) and not flip: - new_list.append(item) - elif int(tmp) > int(value) and flip: + if int(tmp) > int(value): new_list.append(item) else: failed_list.append(item) elif check == "less than": - if int(tmp) < int(value) and not flip: - new_list.append(item) - elif int(tmp) < int(value) and flip: + if int(tmp) < int(value): new_list.append(item) else: failed_list.append(item) @@ -852,12 +773,7 @@ def filter_list(self, input_list, field, check, value, opposite): for file_id in tmp: filedata = self.get_file(file_id) _, ext = os.path.splitext(filedata["filename"]) - if ( - ext.lower().strip() == value.lower().strip() - and not flip - ): - file_list.append(file_id) - elif ext.lower().strip() != value.lower().strip() and flip: + if (ext.lower().strip() == value.lower().strip()): file_list.append(file_id) # else: # failed_list.append(file_id) @@ -876,10 +792,8 @@ def filter_list(self, input_list, field, check, value, opposite): elif type(tmp) == str: filedata = self.get_file(tmp) _, ext = os.path.splitext(filedata["filename"]) - if ext.lower().strip() == value.lower().strip() and not flip: + if ext.lower().strip() == value.lower().strip(): new_list.append(item) - elif ext.lower().strip() != value.lower().strip() and flip: - new_list.append((item, ext)) else: failed_list.append(item) @@ -888,12 +802,11 @@ def filter_list(self, input_list, field, check, value, opposite): failed_list.append(item) # return - if check == "equals any of" and flip: + if flip: tmplist = new_list new_list = failed_list failed_list = tmplist - try: return json.dumps( { @@ -1108,7 +1021,7 @@ def extract_archive(self, file_id, fileformat="zip", password=None): source = z_file.open(member) to_be_uploaded.append( - {"filename": source.name, "data": source.read()} + {"filename": source.name.split("/")[-1], "data": source.read()} ) return_data["success"] = True @@ -1136,7 +1049,7 @@ def extract_archive(self, file_id, fileformat="zip", password=None): source = z_file.open(member) to_be_uploaded.append( - {"filename": source.name, "data": source.read()} + {"filename": source.name.split("/")[-1], "data": source.read()} ) return_data["success"] = True @@ -1163,7 +1076,7 @@ def extract_archive(self, file_id, fileformat="zip", password=None): to_be_uploaded.append( { - "filename": member, + "filename": member.split("/")[-1], "data": member_files.read(), } ) @@ -1188,7 +1101,7 @@ def extract_archive(self, file_id, fileformat="zip", password=None): to_be_uploaded.append( { - "filename": member, + "filename": member.split("/")[-1], "data": member_files.read(), } ) @@ -1217,7 +1130,7 @@ def extract_archive(self, file_id, fileformat="zip", password=None): filename = filename.split("/")[-1] to_be_uploaded.append( { - "filename": item["filename"], + "filename": item["filename"].split("/")[-1], "data": source.read(), } ) From 34f5b502a2a7b7a72183ece571974f4f219cec1c Mon Sep 17 00:00:00 2001 From: frikky Date: Sun, 26 Feb 2023 17:19:00 +0100 Subject: [PATCH 003/259] Added fix for subflows within user input --- shuffle-subflow/1.0.0/src/app.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/shuffle-subflow/1.0.0/src/app.py b/shuffle-subflow/1.0.0/src/app.py index 08d5191a..68fcdcb4 100644 --- a/shuffle-subflow/1.0.0/src/app.py +++ b/shuffle-subflow/1.0.0/src/app.py @@ -43,6 +43,9 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" if len(str(backend_url)) > 0: url = "%s" % (backend_url) + if len(information): + print("Should run arg: %s", information) + if len(email): jsondata = { "targets": [], @@ -93,8 +96,10 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" if len(subflow): print("Should run subflow: %s", subflow) - if len(information): - print("Should run arg: %s", information) + # Missing startnode (user input trigger) + ret = self.run_subflow(self, user_apikey, subflow, information, source_workflow=self.full_execution["workflow"]["id"], source_execution=self.full_execution["execution_id"], source_auth=self.full_execution["authorization"], startnode=startnode, backend_url=backend_url) + result["subflow"] = ret + return json.dumps(result) From 2200863ab72ce8b55ce73e82e86bc64240dace5d Mon Sep 17 00:00:00 2001 From: frikky Date: Mon, 27 Feb 2023 00:59:21 +0100 Subject: [PATCH 004/259] Fixed subflow issue for userinput --- shuffle-subflow/1.0.0/src/app.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/shuffle-subflow/1.0.0/src/app.py b/shuffle-subflow/1.0.0/src/app.py index 68fcdcb4..5588c5fd 100644 --- a/shuffle-subflow/1.0.0/src/app.py +++ b/shuffle-subflow/1.0.0/src/app.py @@ -46,6 +46,20 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" if len(information): print("Should run arg: %s", information) + if len(subflow): + print("Should run subflow: %s", subflow) + + # Missing startnode (user input trigger) + subflows = subflow.split(",") + print("Subflows to run from userinput: ", subflows) + for item in subflows: + # In case of URL being passed, and not just ID + if "/" in item: + item = item.split("/")[-1] + + ret = self.run_subflow(user_apikey, item, information, source_workflow=self.full_execution["workflow"]["id"], source_execution=self.full_execution["execution_id"], source_auth=self.full_execution["authorization"], startnode=startnode, backend_url=backend_url) + result["subflow"] = ret + if len(email): jsondata = { "targets": [], @@ -93,12 +107,6 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" else: result["sms"] = True - if len(subflow): - print("Should run subflow: %s", subflow) - - # Missing startnode (user input trigger) - ret = self.run_subflow(self, user_apikey, subflow, information, source_workflow=self.full_execution["workflow"]["id"], source_execution=self.full_execution["execution_id"], source_auth=self.full_execution["authorization"], startnode=startnode, backend_url=backend_url) - result["subflow"] = ret return json.dumps(result) From ce4856ab8ed67a14656fb82784b38d49f3d8a570 Mon Sep 17 00:00:00 2001 From: frikky Date: Mon, 27 Feb 2023 02:38:35 +0100 Subject: [PATCH 005/259] Fixed subflow to have all relevant info for user input --- shuffle-subflow/1.0.0/src/app.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/shuffle-subflow/1.0.0/src/app.py b/shuffle-subflow/1.0.0/src/app.py index 5588c5fd..68ff6ba9 100644 --- a/shuffle-subflow/1.0.0/src/app.py +++ b/shuffle-subflow/1.0.0/src/app.py @@ -1,9 +1,5 @@ -import asyncio -import time -import random import json import requests -import json from walkoff_app_sdk.app_base import AppBase @@ -43,21 +39,30 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" if len(str(backend_url)) > 0: url = "%s" % (backend_url) - if len(information): - print("Should run arg: %s", information) + print("Found backend url: %s" % url) + #if len(information): + # print("Should run arg: %s", information) if len(subflow): - print("Should run subflow: %s", subflow) + #print("Should run subflow: %s", subflow) # Missing startnode (user input trigger) + #print("Subflows to run from userinput: ", subflows) + subflows = subflow.split(",") - print("Subflows to run from userinput: ", subflows) for item in subflows: # In case of URL being passed, and not just ID if "/" in item: item = item.split("/")[-1] - ret = self.run_subflow(user_apikey, item, information, source_workflow=self.full_execution["workflow"]["id"], source_execution=self.full_execution["execution_id"], source_auth=self.full_execution["authorization"], startnode=startnode, backend_url=backend_url) + argument = json.dumps({ + "information": information, + "parent_workflow": self.full_execution["workflow"]["id"], + "continue_url": "%s/api/v1/workflows/%s/execute?authorization=%s&reference_execution=%s&answer=true" % (url, item, user_apikey, self.full_execution["execution_id"]), + "abort_url": "%s/api/v1/workflows/%s/execute?authorization=%s&reference_execution=%s&answer=true" % (url, item, user_apikey, self.full_execution["execution_id"]), + }) + + ret = self.run_subflow(user_apikey, item, argument, source_workflow=self.full_execution["workflow"]["id"], source_execution=self.full_execution["execution_id"], source_auth=self.full_execution["authorization"], startnode=startnode, backend_url=backend_url) result["subflow"] = ret if len(email): From 317e4920731712e7158ee0d47f8d46766eecbd1e Mon Sep 17 00:00:00 2001 From: frikky Date: Mon, 27 Feb 2023 12:36:43 +0100 Subject: [PATCH 006/259] Fixed base64 conversion problems --- shuffle-tools/1.2.0/src/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 54ca59ba..835d7d20 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -57,7 +57,7 @@ def router(self): def base64_conversion(self, string, operation): if operation == "encode": - encoded_bytes = base64.b64encode(string.encode("utf-8")) + encoded_bytes = base64.b64encode(str(string).encode("utf-8")) encoded_string = str(encoded_bytes, "utf-8") return encoded_string From 292db488e8e2b2eed70177cd67c9d9c83197a339 Mon Sep 17 00:00:00 2001 From: frikky Date: Wed, 8 Mar 2023 17:29:16 +0100 Subject: [PATCH 007/259] Started using 1.1.0 of subflow instead of 1.0.0 due to user input trigger being added --- .../1.1.0}/Dockerfile | 0 shuffle-subflow/1.1.0/api.yaml | 102 ++++++++++ .../1.1.0}/requirements.txt | 0 shuffle-subflow/1.1.0/run | 17 ++ shuffle-subflow/1.1.0/src/app.py | 185 ++++++++++++++++++ unsupported/python3-playground/1.0.0/api.yaml | 36 ---- .../python3-playground/1.0.0/src/app.py | 51 ----- 7 files changed, 304 insertions(+), 87 deletions(-) rename {unsupported/python3-playground/1.0.0 => shuffle-subflow/1.1.0}/Dockerfile (100%) create mode 100644 shuffle-subflow/1.1.0/api.yaml rename {unsupported/python3-playground/1.0.0 => shuffle-subflow/1.1.0}/requirements.txt (100%) create mode 100755 shuffle-subflow/1.1.0/run create mode 100644 shuffle-subflow/1.1.0/src/app.py delete mode 100644 unsupported/python3-playground/1.0.0/api.yaml delete mode 100644 unsupported/python3-playground/1.0.0/src/app.py diff --git a/unsupported/python3-playground/1.0.0/Dockerfile b/shuffle-subflow/1.1.0/Dockerfile similarity index 100% rename from unsupported/python3-playground/1.0.0/Dockerfile rename to shuffle-subflow/1.1.0/Dockerfile diff --git a/shuffle-subflow/1.1.0/api.yaml b/shuffle-subflow/1.1.0/api.yaml new file mode 100644 index 00000000..4148c19a --- /dev/null +++ b/shuffle-subflow/1.1.0/api.yaml @@ -0,0 +1,102 @@ +app_version: 1.0.0 +name: Shuffle Subflow +description: The Shuffle Subflow app +tags: + - Trigger +categories: + - Trigger +contact_info: + name: "@frikkylikeme" + url: https://shuffler.io + email: frikky@shuffler.io +actions: + - name: run_subflow + description: Executes a subflow + parameters: + - name: user_apikey + description: The apikey to use + required: true + multiline: false + example: "REPEATING: Hello world" + schema: + type: string + - name: workflow + description: The Workflow to execute + required: true + multiline: false + example: "REPEATING: Hello world" + schema: + type: string + - name: execution_argument + description: The execution_argument + required: true + multiline: true + example: "REPEATING: Hello world" + schema: + type: string + - name: startnode + description: + required: false + multiline: false + example: "" + schema: + type: string + - name: source_workflow + description: + required: false + multiline: false + example: "" + schema: + type: string + - name: source_execution + description: + required: false + multiline: false + example: "" + schema: + type: string + returns: + schema: + type: string + - name: run_userinput + description: Stops a workflow and notifies the right people + parameters: + - name: user_apikey + description: The apikey to connect back to the APIs + required: true + multiline: false + example: "apikey" + schema: + type: string + - name: sms + description: The numbers to send an sms to + required: false + multiline: false + example: "+474135212,+180241322" + schema: + type: string + - name: email + description: The emails to send an email to + required: false + multiline: false + example: "example@shuffler.io,test@test.com" + schema: + type: string + - name: subflow + description: The subflow IDs to start + required: false + multiline: false + example: "7944b41d-6200-4f28-8973-22ba52637bf0,4832b41d-6200-4f28-8973-22ba52637bf0" + schema: + type: string + - name: information + description: The information to send to the targets + required: false + multiline: true + example: "This is an argument using some liquid: {{ 1 + 2 }} " + schema: + type: string + returns: + schema: + type: string +large_image: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QA/wD/AP+gvaeTAAAAB3RJTUUH4wgeDy4zYzmH5gAADkRJREFUeNrtXV1QG9cV3nt3V2AwkvgRrRE4nTFxMFKATGyLh1gONG8Rxn2qIQ8GOSYdfpy4rknATacPBRx7ak+d2K0dkDWTYvutMRB3nKllYZLaQD2B8GcPJBNwpIxBIPSDEbt39/ZhG+qglZDEjwT4e9Q9e38+3Xv2nHOPjgDGmHiGwADDPYG1hGdkBYFnZAWBZ2QFASrcEyAIgsAYcxyHEGIYhmVZnucJgoAQ0jQtkUgoiiJJEgAQ7mmGiSyEkMPhsFqtIyMjVqvVYvneYrFMTNgYhkHo/2RRFC2RSBSKJKVSqVSmpqRsSU9P37IlRS6XU1QYZr6qQ7pcrpGRke7urp6env7+fpvN5nQ6WBYBQEAICYLw3j6CZcPzPMYETVMymSwhIVGtfjErKys3V7NtW7pUKl21+YNVsLM8Hs/Q0JDJZOrouPPgwZDT6eR5niRJCGGwhwtjzPM8x3EQgrg4WUZGhlarzc/P37EjMzo6em2TNTU1ZTbfvn79emdnp9PpIAhiGbWPoOkIgpBKZbt37y4sLHz11bzExMS1R5bNZmtra7t69crg4ABCiKKoldPQGGOEEEmSKpW6qKhYp9MlJSWtDbLcbveNGzcMhqa+vq8xxqupiRFCAAC1+kW9Xv/667rNmzdHLlk8z//nP93nz583m80sy4TlhUUQBEKIpmmtdm9FReWuXbtIkow4sux2u9F4uampcXJykqYpggizWcSybHx8gl5/SK/XJyQkLEufy0NWX19fQ0N9e7sZACAYAZEA4Q2g1e6tqanNzs5eeodLJQshdOPGjfr6P42OjtI0HW5+RMCybFra1traWp2uYImaYUlkeTyexsbGc+f+MjPjXkbVsOzgOC42Nraq6sibbx7etGlTyP2ETpbL5T59+gOj0cDzOHKOni/wPA8hLCkpPX68Oi4uLrROQiTL6XTW1dU1N38CAIgEFzcQYIwxJoqL3zhx4vcyWShOUig7wu1219XVNTf/HcI1wxRBEAAACMGVK3+vq/uTy+UKoYegyfJ4PKdOfdDc/Ing+YabgeAXDOGVK82nT5/yeDxBPxuUNM/zjY0fG40GAMBaZEoAAITRePnSpYtCLChwBEdWa2vruXPneB6vodPnDQAAz/MfffTh9evXg3swcAXf19d3+PCbjx6NRbKVEDg4jlMqUy9d+jgnJyfARwLdWXb7VEND/ejod+uDKYIgSJJ89Gjs5MmGqampAB8JiCyMsdFobG83R6aNHjJomr5zp91gaApQeQVEVldXV1NT45rWU75AkqTB0NTZeS8Q4cXJcrvdFy6cn5ycjHwzPQQAAOx2+4ULF9xu96LCi6//xo3PzGYzTUfEpdlKgKbp9nZzS0vLopKLkGWz2QwGA8sya9eqCgQIIaPx8sTEuH+xRchqa2vr6/s6XDHPVQNFUQMD/a2trf7F/JE1OTl59eqVDZKThDG+du2azWbzI+OPLLPZPDg4sO63lQCKogYHB0wmkx8Zn2R5PJ6Wlk8RQuFexeqB47iWluuzs7O+BHySNTQ01NnZtUG2lQCKorq7uwcHB30J+CTLZDI5nY51aYj6AgDA6XSYTLd8CYiT5XK5OjruhHvyYQAAoKOjw+l0iraKkzUyMvLgwdC68ZkDB0mSDx8+HB4eFm0VJ6u7u8vpdG6oMyhAOIn37om7iiJkIYR6enqCjSKuG2CMe3t7WJb1bhIhy+Fw9Pf3b8AzKIAkyYGBgenpae8mEbKsVqvNZluXMYZAACG026csFotIk/dHIyMjG81oeBoAAIfD8c0333g3idicVqsVISSRSIIdBgvXmF5ji2aKBig5D57nMcZ+Ek9E+wykZ29wHGe1WgMiy2L5PliaCILgeS4uTuad2DkzMzM7++Snc8VSqVQiiVogyTCMyyVi4HAcR9P0z3++RSKRzM3NjY8/5jhuAWUY402bNsXGxhGEyHfAMHO+TCdfsFpFSFhIFkLIYrEEq7AQQhkZO+rq6lJSlDzPz6cdkyR55syfr127+nTwHmPi2LHjr7322tMvXIqiPv/88z/+8Q/eTKlU6oqKSo1GExMT43a7zWbz2bNnrFbL068glmX37/9VdfW7QpYpxgRBYGFD2e32+vq6L7/8IvBXFoRwbGxMyO5chKyJCVuAnQrgef7557d/8MGpl19+WRhJWKSQI7l5c+yC04ExoVAotm7dKqQeCx+SJOmdcsbzfGpq6pkzZ9VqtcfjmZmZUSgUxcXFiYmJ77zzttvtmt+wGOOYmNi0tDSOQwQBBF54nnc4HB9+eO7u3X8H+3K326dZll2ELIZhGIYJ6oQDACiKbGy89Ne/sjt37iovr3A4HGfPnhFuGIeGhnx5452dnZcu/Q0AgDGGED5+/Nj7m9Nq96rVarvdXl19/P79+zpdwdGjRzUaTWZm5t27X1LU/zYsTdNffNFRVvYmx3GpqWlHj/5WLpdfvHjxn//8rKenJ9iXFQBgbo5hGGZBftLCZbAsixAbRMcEAQB48ODB4OAAw7Acx5eXV8zNzd2+fWtoaIgkSZKkfB3q6enpnp7e+XHt9kkISa/OIUEQEokkKyvLarW2trbcv98VHR09PDz8tDCEcHT0u2+/HeE47oUXMsrLK4Qv4+7du6ElZCHEetulC8nieT4E250kSZIkhUSV//VLUTQt8b/58/Lybt78nPjRDvzNb8oW+FgURZlM/+rs7NRoNFVVR/T6Q6Ojo7du/au5+RObbXLBhoUQQiiBkKMo8sdPQMghJoQ4Qf39ZIjQ+lodQAitVutbb5WdPHnyq6++Qgjt2LGjquqI0fjJCy9keC9mpbGQeAjhqtnut2/frq2tEbYSy7IOh2PB0BzH/fKXr+XkvDQ2NvrGG8XJycn5+fnl5RUZGRmvv65b0bgIRZHenS8ki6bpea250pDJZFlZWQAQGBMQwvHx8a+/7v0pWfxzz/3i2LFjLpeLpumbN2+aTKZf//pAUlJSVFTQNnOQZNHeuQoLyZJIJBKJJOQbHUHhBPjyyc3N3bVr14+Toz799B9HjlT9dMbkZ5+1FRQUaDSaurr6t99+Jzo6WqFQ2Gw2k8m0cicAYxwVJfH2YRaSRVGUQhH6D1+ePHkyNjY2NTXJsj5vOgAgbDabxWJ5WulQFOXt6EMIx8cf/+53xyorq/bs2SOXyxFCnZ2dFy/+rbu7y9cZZFlksVgYhnny5EnIC4mPl3vvLJH8rNraGoOhKTTfUCKRyGRyjPH0tN23AsaCY/T00ACAuTmPqFPCcRxFUcnJP4uKiuI4bmpq0uVy+XnNkSQpl8cL/jDDzIUQEWAYpqSkpKHh1IJHRYZUKlOD7X1+wQzDjI8/Jn60430JOp0Oh2Pa+3HRhZEkiTH+4QerQC6E0L9BwHGccBEfciY1xkRKSqr3oyKjpqRsCdk8CXB+ISwjKA21RHVGUaRSqRTp1vujbdvSZTLZBrm19wbGWCaTbdu2zbtJhKyUlJSEhMQNG4PneT4+PiHQnSWXy9Vq9erbxxECjuNUKpVcLvduEiGLoqisrGwIN25YOTs7RzR7VlwRajSauLiNqLYwxlKpNDdXI9oqTlZ6enpGRhg81bCD47jt27enpz8v2ipOllQq1Wq14Z55GIAx1mr3ymQy0Vaf9kheXr5UurFOIsZYKpXl5eX7EvBJVmZm5u7duzdUMhtCaOfOnZmZKl8CPsmKjo7et69wQ13iQ0gWFu6PifEZhvbnFuTlvapSqTfI5kIIZWZm5uXl+ZHxR1ZiYtKBA0Ub5B4fAFBUVKRQKPzILOJw6nQFavWL635zIYRUKrVOV+BfbBGyFIqk0tLSdZ+GS5LUwYMlycnJ/sUWD2XodAV7974qmty1PsCyrFar3bdv36KSi5O1efPm8vKK+PiEdWlzYYzj4xMqKioDKfYQUJBMo9Ho9fp16f1wHFdSUpqbmxuIcEBkQQj1+kNa7d51dhhZln3llT2HDh0KMLIaaPg1ISHhvfdq0tK2rpv9JaSQ1NTUBl75LohYdU5OTk1NbWxs7DoIovI8HxMT+957NS+99FLgTwUX2C8sLKyqOgIhXNPKXki3rKys3L9/f1APBkcWhPDw4bKSktK1zBWBMT54sKSs7K1gPd+gr4yio6OPH68uLn5jjR5GnucPHCiurn43hLytUEzzuLi4Eyd+TxDElSvNABBrxXkU0pkPHCh+//33Q6ubu5RiY67Tp08ZjZeFOl7hpmIR8DwPADh4sKS6+t2QKwwvqYzd7Ozsxx9f+uijD2dmZiI58sVxXExMbGVlZVnZW+EpYycAIdTW1lZfX/fo0VhkFl9hWVawp/bt2xfOAonz6O3tbWiov3OnPUIKtwsQSm++8sqe2toTgZcy8oNlK+o6NTVlMDQZDE12uz0StphQ1LW0tFSvP7Rc1amXs1wwx3FdXV0XLpxvbzd7/zxh1YAQIklKq9VWVlbu3q2JxHLB83C73a2trZcvGwYG+sNSiFqlUpeUlOp0umWvq79SJc4nJiba2lqvXr06ODggpO6tdIlzCMnMzMyioqKCggKFInnp3a4eWQImJydNplstLS1dXV3CbxiXvXi+cDO6c+fOwsL9+fn5K1QJfjXIEjA7Ozs4OGgy3ero6Hj48KHT6cAYL/FvGQAAUql0+/btWu3evLx8lUq1FAMqgsiah9PpHB4evnfvXm9v78BAv90+5XA4EOIC/MMPiiJlMll8fIJKpc7Ozs7NzU1PT/eVl7DmyZoHy7LT09NWq2Vk5Bur1WK1WsbGxuz26bk5BiEWIY4gCIoiKYqOipLEx8vT0tJSUlKVSmV6+raUFKVcLg+LdRIeshZA0D4sywp/UiQEY0mSFP6kiKbpCLF1I4KstYJIjxZEFJ6RFQSekRUEnpEVBP4LiQWypqHC6doAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTktMDgtMzBUMTU6NDc6MjQtMDQ6MDCzXTa0AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE5LTA4LTMwVDE1OjQ2OjUxLTA0OjAwdT/DiAAAAABJRU5ErkJggg== diff --git a/unsupported/python3-playground/1.0.0/requirements.txt b/shuffle-subflow/1.1.0/requirements.txt similarity index 100% rename from unsupported/python3-playground/1.0.0/requirements.txt rename to shuffle-subflow/1.1.0/requirements.txt diff --git a/shuffle-subflow/1.1.0/run b/shuffle-subflow/1.1.0/run new file mode 100755 index 00000000..e73f748d --- /dev/null +++ b/shuffle-subflow/1.1.0/run @@ -0,0 +1,17 @@ +#!/bin/sh +docker stop frikky/shuffle:testing_1.0.0 --force +docker rm frikky/shuffle:testing_1.0.0 --force +docker rmi frikky/shuffle:testing_1.0.0 --force + +docker build . -t frikky/shuffle:testing_1.0.0 + +echo "RUNNING!\n\n" +docker run \ + --env CALLBACK_URL="http://192.168.239.144:5001" \ + --env ACTION='{"app_name":"testing","app_version":"1.0.0","errors":[],"id_":"13fa4c3f-8991-3ade-b90d-f326fd4941dd","is_valid":true,"label":"random_number","environment":"onprem","name":"random_number","parameters":[],"position":{"x":178.07868996109607,"y":457.28345902971614},"priority":3}' \ + --env FUNCTION_APIKEY="asdasd" \ + --env EXECUTIONID="2349bf96-51ad-68d2-5ca6-75ef8f7ee814" \ + --env AUTHORIZATION="8e344a2e-db51-448f-804c-eb959a32c139" \ + frikky/shuffle:testing_1.0.0 + +docker push frikky/shuffle:testing_1.0.0 diff --git a/shuffle-subflow/1.1.0/src/app.py b/shuffle-subflow/1.1.0/src/app.py new file mode 100644 index 00000000..68ff6ba9 --- /dev/null +++ b/shuffle-subflow/1.1.0/src/app.py @@ -0,0 +1,185 @@ +import json +import requests + +from walkoff_app_sdk.app_base import AppBase + +class Subflow(AppBase): + """ + An example of a Walkoff App. + Inherit from the AppBase class to have Redis, logging, and console logging set up behind the scenes. + """ + __version__ = "1.0.0" + app_name = "subflow" # this needs to match "name" in api.yaml + + def __init__(self, redis, logger, console_logger=None): + """ + Each app should have this __init__ to set up Redis and logging. + :param redis: + :param logger: + :param console_logger: + """ + super().__init__(redis, logger, console_logger) + + # Should run user input + def run_userinput(self, user_apikey, sms="", email="", subflow="", information="", startnode="", backend_url=""): + #url = "%s/api/v1/workflows/%s/execute" % (self.url, workflow) + + headers = { + "Authorization": "Bearer %s" % user_apikey, + "User-Agent": "Shuffle Userinput 1.0.0" + } + + result = { + "success": True, + "source": "userinput", + "reason": "Userinput data sent and workflow paused. Waiting for user input before continuing workflow." + } + + url = self.url + if len(str(backend_url)) > 0: + url = "%s" % (backend_url) + + print("Found backend url: %s" % url) + #if len(information): + # print("Should run arg: %s", information) + + if len(subflow): + #print("Should run subflow: %s", subflow) + + # Missing startnode (user input trigger) + #print("Subflows to run from userinput: ", subflows) + + subflows = subflow.split(",") + for item in subflows: + # In case of URL being passed, and not just ID + if "/" in item: + item = item.split("/")[-1] + + argument = json.dumps({ + "information": information, + "parent_workflow": self.full_execution["workflow"]["id"], + "continue_url": "%s/api/v1/workflows/%s/execute?authorization=%s&reference_execution=%s&answer=true" % (url, item, user_apikey, self.full_execution["execution_id"]), + "abort_url": "%s/api/v1/workflows/%s/execute?authorization=%s&reference_execution=%s&answer=true" % (url, item, user_apikey, self.full_execution["execution_id"]), + }) + + ret = self.run_subflow(user_apikey, item, argument, source_workflow=self.full_execution["workflow"]["id"], source_execution=self.full_execution["execution_id"], source_auth=self.full_execution["authorization"], startnode=startnode, backend_url=backend_url) + result["subflow"] = ret + + if len(email): + jsondata = { + "targets": [], + "body": information, + "subject": "User input required", + "type": "User input", + "start": startnode, + "workflow_id": self.full_execution["workflow"]["id"], + "reference_execution": self.full_execution["execution_id"], + } + + for item in email.split(","): + jsondata["targets"].append(item.strip()) + + print("Should run email with targets: %s", jsondata["targets"]) + + ret = requests.post("%s/api/v1/functions/sendmail" % url, json=jsondata, headers=headers) + if ret.status_code != 200: + print("Failed sending email. Data: %s" % ret.text) + result["email"] = False + else: + result["email"] = True + + if len(sms) > 0: + print("Should run SMS: %s", sms) + + jsondata = { + "numbers": [], + "body": information, + "type": "User input", + "start": startnode, + "workflow_id": self.full_execution["workflow"]["id"], + "reference_execution": self.full_execution["execution_id"], + } + + for item in sms.split(","): + jsondata["numbers"].append(item.strip()) + + print("Should send sms with targets: %s", jsondata["numbers"]) + + ret = requests.post("%s/api/v1/functions/sendsms" % url, json=jsondata, headers=headers) + if ret.status_code != 200: + print("Failed sending email. Data: %s" % ret.text) + result["sms"] = False + else: + result["sms"] = True + + + + return json.dumps(result) + + def run_subflow(self, user_apikey, workflow, argument, source_workflow="", source_execution="", source_node="", source_auth="", startnode="", backend_url=""): + #print("STARTNODE: %s" % startnode) + url = "%s/api/v1/workflows/%s/execute" % (self.url, workflow) + + params = {} + if len(str(source_workflow)) > 0: + params["source_workflow"] = source_workflow + else: + print("No source workflow") + + if len(str(source_auth)) > 0: + params["source_auth"] = source_auth + else: + print("No source auth") + + if len(str(source_node)) > 0: + params["source_node"] = source_node + else: + print("No source node") + + if len(str(source_execution)) > 0: + params["source_execution"] = source_execution + else: + print("No source execution") + + if len(str(startnode)) > 0: + params["start"] = startnode + else: + print("No startnode") + + if len(str(backend_url)) > 0: + url = "%s/api/v1/workflows/%s/execute" % (backend_url, workflow) + print("[INFO] Changed URL to %s for this execution" % url) + + headers = { + "Authorization": "Bearer %s" % user_apikey, + "User-Agent": "Shuffle Subflow 1.0.0" + } + + if len(str(argument)) == 0: + ret = requests.post(url, headers=headers, params=params) + else: + if not isinstance(argument, list) and not isinstance(argument, dict): + try: + argument = json.loads(argument) + except: + pass + + #print(f"ARG: {argument}") + try: + ret = requests.post(url, headers=headers, params=params, json=argument) + print(f"Successfully sent argument of length {len(str(argument))} as JSON") + except: + try: + ret = requests.post(url, headers=headers, json=argument, params=params) + print("Successfully sent as JSON (2)") + except: + ret = requests.post(url, headers=headers, data=argument, params=params) + print("Successfully sent as data (3)") + + print("Status: %d" % ret.status_code) + print("RET: %s" % ret.text) + + return ret.text + +if __name__ == "__main__": + Subflow.run() diff --git a/unsupported/python3-playground/1.0.0/api.yaml b/unsupported/python3-playground/1.0.0/api.yaml deleted file mode 100644 index 83797dd0..00000000 --- a/unsupported/python3-playground/1.0.0/api.yaml +++ /dev/null @@ -1,36 +0,0 @@ -app_version: 1.0.0 -name: Python3 playground -description: A test app made for you to personally change the code -contact_info: - name: "@frikkylikeme" - url: https://shuffler.io - email: frikky@shuffler.io -tags: - - Testing -categories: - - Testing -actions: - - name: run_python_script - description: Runs a python script defined by YOU - parameters: - - name: json_data - description: The JSON to handle - required: true - multiline: true - example: '{"data": "testing"}' - schema: - type: string - - name: function_to_execute - description: The selected python function to run - required: true - multiline: true - example: '1' - options: - - function_1 - - function_2 - schema: - type: string - returns: - schema: - type: string -large_image: data:image/jpg;base64,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 diff --git a/unsupported/python3-playground/1.0.0/src/app.py b/unsupported/python3-playground/1.0.0/src/app.py deleted file mode 100644 index 356a6cfe..00000000 --- a/unsupported/python3-playground/1.0.0/src/app.py +++ /dev/null @@ -1,51 +0,0 @@ -import socket -import asyncio -import time -import random -import json - -from walkoff_app_sdk.app_base import AppBase - -class PythonPlayground(AppBase): - __version__ = "1.0.0" - app_name = "python_playground" # this needs to match "name" in api.yaml - - def __init__(self, redis, logger, console_logger=None): - """ - Each app should have this __init__ to set up Redis and logging. - :param redis: - :param logger: - :param console_logger: - """ - super().__init__(redis, logger, console_logger) - - def run_me_1(self, json_data): - return "Ran function 1" - - def run_me_2(self, json_data): - return "Ran function 2" - - def run_me_3(self, json_data): - return "Ran function 3" - - # Write your data inside this function - def run_python_script(self, json_data, function_to_execute): - # It comes in as a string, so needs to be set to JSON - if not isinstance(json_data, list) and not isinstance(json_data, object) and not isinstance(json_data, dict): - try: - json_data = json.loads(json_data) - except json.decoder.JSONDecodeError as e: - return "Couldn't decode json: %s" % e - - # These are functions - switcher = { - "function_1" : self.run_me_1, - "function_2" : self.run_me_2, - "function_3" : self.run_me_3, - } - - func = switcher.get(function_to_execute, lambda: "Invalid function") - return func(json_data) - -if __name__ == "__main__": - PythonPlayground.run() From b346f139a55896b4f11e93b3c61fe7f727921364 Mon Sep 17 00:00:00 2001 From: frikky Date: Thu, 9 Mar 2023 03:22:47 +0100 Subject: [PATCH 008/259] Finalized most of the user-input configurations --- shuffle-subflow/1.1.0/src/app.py | 33 +++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/shuffle-subflow/1.1.0/src/app.py b/shuffle-subflow/1.1.0/src/app.py index 68ff6ba9..6f2abb50 100644 --- a/shuffle-subflow/1.1.0/src/app.py +++ b/shuffle-subflow/1.1.0/src/app.py @@ -21,18 +21,26 @@ def __init__(self, redis, logger, console_logger=None): super().__init__(redis, logger, console_logger) # Should run user input - def run_userinput(self, user_apikey, sms="", email="", subflow="", information="", startnode="", backend_url=""): + def run_userinput(self, user_apikey, sms="", email="", subflow="", information="", startnode="", backend_url="", source_node=""): #url = "%s/api/v1/workflows/%s/execute" % (self.url, workflow) headers = { "Authorization": "Bearer %s" % user_apikey, - "User-Agent": "Shuffle Userinput 1.0.0" + "User-Agent": "Shuffle Userinput 1.1.0" } result = { "success": True, "source": "userinput", - "reason": "Userinput data sent and workflow paused. Waiting for user input before continuing workflow." + "reason": "Userinput data sent and workflow paused. Waiting for user input before continuing workflow.", + "information": information, + "click_info": { + "clicked": False, + "time": "", + "ip": "", + "user": "", + "note": "", + } } url = self.url @@ -50,20 +58,29 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" #print("Subflows to run from userinput: ", subflows) subflows = subflow.split(",") + frontend_url = url + if ":5001" in frontend_url: + print("Should change port to 3001.") + for item in subflows: # In case of URL being passed, and not just ID if "/" in item: item = item.split("/")[-1] + # Subflow should be the subflow to run + # Workflow in the URL should be the source workflow argument = json.dumps({ "information": information, "parent_workflow": self.full_execution["workflow"]["id"], - "continue_url": "%s/api/v1/workflows/%s/execute?authorization=%s&reference_execution=%s&answer=true" % (url, item, user_apikey, self.full_execution["execution_id"]), - "abort_url": "%s/api/v1/workflows/%s/execute?authorization=%s&reference_execution=%s&answer=true" % (url, item, user_apikey, self.full_execution["execution_id"]), + "frontend_continue": "%s/workflows/%s/run?authorization=%s&reference_execution=%s&answer=true" % (frontend_url, self.full_execution["workflow"]["id"], user_apikey, self.full_execution["execution_id"]), + "frontend_abort": "%s/workflows/%s/run?authorization=%s&reference_execution=%s&answer=true" % (frontend_url, self.full_execution["workflow"]["id"], user_apikey, self.full_execution["execution_id"]), + "api_continue": "%s/api/v1/workflows/%s/execute?authorization=%s&reference_execution=%s&answer=true" % (frontend_url, self.full_execution["workflow"]["id"], user_apikey, self.full_execution["execution_id"]), + "api_abort": "%s/api/v1/workflows/%s/execute?authorization=%s&reference_execution=%s&answer=true" % (frontend_url, self.full_execution["workflow"]["id"], user_apikey, self.full_execution["execution_id"]), }) - ret = self.run_subflow(user_apikey, item, argument, source_workflow=self.full_execution["workflow"]["id"], source_execution=self.full_execution["execution_id"], source_auth=self.full_execution["authorization"], startnode=startnode, backend_url=backend_url) + ret = self.run_subflow(user_apikey, item, argument, source_workflow=self.full_execution["workflow"]["id"], source_execution=self.full_execution["execution_id"], source_auth=self.full_execution["authorization"], startnode=startnode, backend_url=backend_url, source_node=source_node) result["subflow"] = ret + result["subflow_url"] = "%s/workflows/%s" % (frontend_url, item) if len(email): jsondata = { @@ -74,6 +91,7 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" "start": startnode, "workflow_id": self.full_execution["workflow"]["id"], "reference_execution": self.full_execution["execution_id"], + "authorization": self.full_execution["authorization"], } for item in email.split(","): @@ -98,6 +116,7 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" "start": startnode, "workflow_id": self.full_execution["workflow"]["id"], "reference_execution": self.full_execution["execution_id"], + "authorization": self.full_execution["authorization"], } for item in sms.split(","): @@ -152,7 +171,7 @@ def run_subflow(self, user_apikey, workflow, argument, source_workflow="", sourc headers = { "Authorization": "Bearer %s" % user_apikey, - "User-Agent": "Shuffle Subflow 1.0.0" + "User-Agent": "Shuffle Subflow 1.1.0" } if len(str(argument)) == 0: From 279ef57995b3a2a087e49a06b9837bb98e043b4a Mon Sep 17 00:00:00 2001 From: frikky Date: Tue, 14 Mar 2023 18:25:39 +0100 Subject: [PATCH 009/259] Fixed minor issue in subflow/userinput --- shuffle-subflow/1.1.0/src/app.py | 4 +++- shuffle-tools/1.2.0/src/app.py | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/shuffle-subflow/1.1.0/src/app.py b/shuffle-subflow/1.1.0/src/app.py index 6f2abb50..7c0a609c 100644 --- a/shuffle-subflow/1.1.0/src/app.py +++ b/shuffle-subflow/1.1.0/src/app.py @@ -8,7 +8,7 @@ class Subflow(AppBase): An example of a Walkoff App. Inherit from the AppBase class to have Redis, logging, and console logging set up behind the scenes. """ - __version__ = "1.0.0" + __version__ = "1.1.0" app_name = "subflow" # this needs to match "name" in api.yaml def __init__(self, redis, logger, console_logger=None): @@ -61,6 +61,8 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" frontend_url = url if ":5001" in frontend_url: print("Should change port to 3001.") + if "appspot.com" in frontend_url: + frontend_url = "https://shuffler.io" for item in subflows: # In case of URL being passed, and not just ID diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 835d7d20..5ec5bd0c 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -2290,7 +2290,6 @@ def get_standardized_data(self, json_input, input_type): json_input = json.loads(json_input, strict=False) input_synonyms = self.get_synonyms(input_type) - parsed_data, important_fields = self.run_key_recursion(json_input, input_synonyms) # Try base64 decoding and such too? From 5e46af9a09cbd8f1119e0f4bd3fd71be9601ff9a Mon Sep 17 00:00:00 2001 From: frikky Date: Wed, 15 Mar 2023 18:58:00 +0100 Subject: [PATCH 010/259] Added email header analysis for spf, dkim, dmarc and spoofing --- email/1.2.0/api.yaml | 9 +++ email/1.2.0/src/analyze.py | 132 +++++++++++++++++++++++++++++++++++++ email/1.2.0/src/app.py | 84 +++++++++++++++++++++++ 3 files changed, 225 insertions(+) create mode 100644 email/1.2.0/src/analyze.py diff --git a/email/1.2.0/api.yaml b/email/1.2.0/api.yaml index 798b12c8..d19e7e7b 100644 --- a/email/1.2.0/api.yaml +++ b/email/1.2.0/api.yaml @@ -265,6 +265,15 @@ actions: returns: schema: type: string + - name: analyze_headers + description: + parameters: + - name: headers + description: Email headers in any format + required: true + multiline: true + schema: + type: string returns: schema: type: string diff --git a/email/1.2.0/src/analyze.py b/email/1.2.0/src/analyze.py new file mode 100644 index 00000000..624e82c4 --- /dev/null +++ b/email/1.2.0/src/analyze.py @@ -0,0 +1,132 @@ +import json +import eml_parser +import datetime + +def json_serial(obj): + if isinstance(obj, datetime.datetime): + serial = obj.isoformat() + return serial + +# 1. +# "headers": { +# "headername": ["asd"] +# } + +# 2. +# "headers": [ +# "key": "headerame", +# "value": "headervalue" +# ] + +# 3. +# Raw headers + +def parse_email_headers(email_headers): + try: + email_headers = bytes(email_headers,'utf-8') + ep = eml_parser.EmlParser() + parsed_headers = ep.decode_email_bytes(email_headers) + return json.dumps(parsed_headers, default=json_serial) + except Exception as e: + raise Exception(e) + +# Basic function to check headers in an email +# Can be dumped in in pretty much any format +def analyze_headers(headers): + # Raw + if isinstance(headers, str): + headers = parse_email_headers(headers) + if isinstance(headers, str): + headers = json.loads(headers) + + headers = headers["header"]["header"] + + # Just a way to parse out shitty email formats + if "header" in headers: + headers = headers["header"] + if "header" in headers: + headers = headers["header"] + + if not isinstance(headers, list): + newheaders = [] + for key, value in headers.items(): + if isinstance(value, list): + newheaders.append({ + "key": key, + "value": value[0], + }) + else: + newheaders.append({ + "key": key, + "value": value, + }) + + headers = newheaders + + spf = False + dkim = False + dmarc = False + spoofed = False + for item in headers: + if "name" in item: + item["key"] = item["name"] + + item["key"] = item["key"].lower() + + if "spf" in item["key"]: + if "pass " in item["value"].lower(): + spf = True + + if "dkim" in item["key"]: + if "pass " in item["value"].lower(): + dkim = True + + if "dmarc" in item["key"]: + print("dmarc: ", item["key"]) + + if item["key"] == "authentication-results": + if "spf=pass" in item["value"]: + spf = True + if "dkim=pass" in item["value"]: + dkim = True + if "dmarc=pass" in item["value"]: + dmarc = True + + # Fix spoofed! + if item["key"] == "from": + print("From: " + item["value"]) + for subitem in headers: + if "name" in subitem: + subitem["key"] = subitem["name"] + + if subitem["key"] == "reply-to": + print("Reply-To: " + subitem["value"]) + break + + analyzed_headers = { + "success": True, + "spf": spf, + "dkim": dkim, + "dmarc": dmarc, + "spoofed": spoofed + } + + # Should be a dictionary + return analyzed_headers + + +with open("hdr.txt", "r") as tmp: + data = json.loads(tmp.read()) + print(analyze_headers(data)) + +print() + +with open("hdr2.txt", "r") as tmp: + data = json.loads(tmp.read()) + print(analyze_headers(data)) + +print() + +with open("hdr3.txt", "r") as tmp: + data = tmp.read() + print(analyze_headers(data)) diff --git a/email/1.2.0/src/app.py b/email/1.2.0/src/app.py index 9fd12543..5b00c30a 100644 --- a/email/1.2.0/src/app.py +++ b/email/1.2.0/src/app.py @@ -436,6 +436,90 @@ def parse_email_headers(self, email_headers): except Exception as e: raise Exception(e) + # Basic function to check headers in an email + # Can be dumped in in pretty much any format + def analyze_headers(headers): + # Raw + if isinstance(headers, str): + headers = parse_email_headers(headers) + if isinstance(headers, str): + headers = json.loads(headers) + + headers = headers["header"]["header"] + + # Just a way to parse out shitty email formats + if "header" in headers: + headers = headers["header"] + if "header" in headers: + headers = headers["header"] + + if not isinstance(headers, list): + newheaders = [] + for key, value in headers.items(): + if isinstance(value, list): + newheaders.append({ + "key": key, + "value": value[0], + }) + else: + newheaders.append({ + "key": key, + "value": value, + }) + + headers = newheaders + + spf = False + dkim = False + dmarc = False + spoofed = False + for item in headers: + if "name" in item: + item["key"] = item["name"] + + item["key"] = item["key"].lower() + + if "spf" in item["key"]: + if "pass " in item["value"].lower(): + spf = True + + if "dkim" in item["key"]: + if "pass " in item["value"].lower(): + dkim = True + + if "dmarc" in item["key"]: + print("dmarc: ", item["key"]) + + if item["key"] == "authentication-results": + if "spf=pass" in item["value"]: + spf = True + if "dkim=pass" in item["value"]: + dkim = True + if "dmarc=pass" in item["value"]: + dmarc = True + + # Fix spoofed! + if item["key"] == "from": + print("From: " + item["value"]) + for subitem in headers: + if "name" in subitem: + subitem["key"] = subitem["name"] + + if subitem["key"] == "reply-to": + print("Reply-To: " + subitem["value"]) + break + + analyzed_headers = { + "success": True, + "spf": spf, + "dkim": dkim, + "dmarc": dmarc, + "spoofed": spoofed + } + + # Should be a dictionary + return analyzed_headers + # Run the actual thing after we've checked params def run(request): From f18ee5b20a170533326590998c8eed3fd5714e4f Mon Sep 17 00:00:00 2001 From: frikky Date: Wed, 15 Mar 2023 21:07:06 +0100 Subject: [PATCH 011/259] Fixed analyze header action for spf, dkim and dmarc~. Still missing spoofing check for sender --- email/1.2.0/src/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/email/1.2.0/src/app.py b/email/1.2.0/src/app.py index 5b00c30a..46fd14ab 100644 --- a/email/1.2.0/src/app.py +++ b/email/1.2.0/src/app.py @@ -438,10 +438,10 @@ def parse_email_headers(self, email_headers): # Basic function to check headers in an email # Can be dumped in in pretty much any format - def analyze_headers(headers): + def analyze_headers(self, headers): # Raw if isinstance(headers, str): - headers = parse_email_headers(headers) + headers = self.parse_email_headers(headers) if isinstance(headers, str): headers = json.loads(headers) From 6e6aa0d73168b98f042f123d4661b98e1427729d Mon Sep 17 00:00:00 2001 From: frikky Date: Thu, 23 Mar 2023 13:02:41 +0100 Subject: [PATCH 012/259] Added more GPG configs --- email/1.2.0/src/analyze.py | 68 ++++++++---- email/1.2.0/src/app.py | 46 ++++++-- gpg-tools/1.0.0/src/app.py | 4 +- gpg-tools/README.md | 14 +++ gpg-tools/gpgtest/gpg_run.py | 105 ++++++++++++++++++ .../1.0.0/src/app.py | 6 +- shuffle-tools/1.2.0/api.yaml | 2 +- shuffle-tools/1.2.0/src/app.py | 2 +- 8 files changed, 208 insertions(+), 39 deletions(-) create mode 100644 gpg-tools/README.md create mode 100644 gpg-tools/gpgtest/gpg_run.py diff --git a/email/1.2.0/src/analyze.py b/email/1.2.0/src/analyze.py index 624e82c4..77e3169e 100644 --- a/email/1.2.0/src/analyze.py +++ b/email/1.2.0/src/analyze.py @@ -63,10 +63,16 @@ def analyze_headers(headers): headers = newheaders + spf = False dkim = False dmarc = False spoofed = False + + analyzed_headers = { + "success": True, + } + for item in headers: if "name" in item: item["key"] = item["name"] @@ -95,21 +101,41 @@ def analyze_headers(headers): # Fix spoofed! if item["key"] == "from": print("From: " + item["value"]) + + if "<" in item["value"]: + item["value"] = item["value"].split("<")[1] + for subitem in headers: if "name" in subitem: subitem["key"] = subitem["name"] + + subitem["key"] = subitem["key"].lower() + print("Found: ", subitem["key"]) + if subitem["key"] == "reply-to": - print("Reply-To: " + subitem["value"]) - break - - analyzed_headers = { - "success": True, - "spf": spf, - "dkim": dkim, - "dmarc": dmarc, - "spoofed": spoofed - } + if "<" in subitem["value"]: + subitem["value"] = subitem["value"].split("<")[1] + + print("Reply-To: " + subitem["value"], item["value"]) + if item["value"] != subitem["value"]: + spoofed = True + analyzed_headers["spoofed_reason"] = "Reply-To is different than From" + break + + if subitem["key"] == "mail-reply-to": + if "<" in subitem["value"]: + subitem["value"] = subitem["value"].split("<")[1] + + if item["value"] != subitem["value"]: + spoofed = True + analyzed_headers["spoofed_reason"] = "Mail-Reply-To is different than From" + break + + analyzed_headers["spf"] = spf + analyzed_headers["dkim"] = dkim + analyzed_headers["dmarc"] = dmarc + analyzed_headers["spoofed"] = spoofed # Should be a dictionary return analyzed_headers @@ -119,14 +145,14 @@ def analyze_headers(headers): data = json.loads(tmp.read()) print(analyze_headers(data)) -print() - -with open("hdr2.txt", "r") as tmp: - data = json.loads(tmp.read()) - print(analyze_headers(data)) - -print() - -with open("hdr3.txt", "r") as tmp: - data = tmp.read() - print(analyze_headers(data)) + print() +# +#with open("hdr2.txt", "r") as tmp: +# data = json.loads(tmp.read()) +# print(analyze_headers(data)) +# +# print() +# +#with open("hdr3.txt", "r") as tmp: +# data = tmp.read() +# print(analyze_headers(data)) diff --git a/email/1.2.0/src/app.py b/email/1.2.0/src/app.py index 46fd14ab..4a17f0f6 100644 --- a/email/1.2.0/src/app.py +++ b/email/1.2.0/src/app.py @@ -468,11 +468,17 @@ def analyze_headers(self, headers): }) headers = newheaders + spf = False dkim = False dmarc = False spoofed = False + + analyzed_headers = { + "success": True, + } + for item in headers: if "name" in item: item["key"] = item["name"] @@ -501,21 +507,41 @@ def analyze_headers(self, headers): # Fix spoofed! if item["key"] == "from": print("From: " + item["value"]) + + if "<" in item["value"]: + item["value"] = item["value"].split("<")[1] + for subitem in headers: if "name" in subitem: subitem["key"] = subitem["name"] + + subitem["key"] = subitem["key"].lower() if subitem["key"] == "reply-to": - print("Reply-To: " + subitem["value"]) - break - - analyzed_headers = { - "success": True, - "spf": spf, - "dkim": dkim, - "dmarc": dmarc, - "spoofed": spoofed - } + + if "<" in subitem["value"]: + subitem["value"] = subitem["value"].split("<")[1] + + if item["value"] != subitem["value"]: + spoofed = True + analyzed_headers["spoofed_reason"] = "Reply-To is different than From" + break + + if subitem["key"] == "mail-reply-to": + print("Reply-To: " + subitem["value"], item["value"]) + + if "<" in subitem["value"]: + subitem["value"] = subitem["value"].split("<")[1] + + if item["value"] != subitem["value"]: + spoofed = True + analyzed_headers["spoofed_reason"] = "Mail-Reply-To is different than From" + break + + analyzed_headers["spf"] = spf + analyzed_headers["dkim"] = dkim + analyzed_headers["dmarc"] = dmarc + analyzed_headers["spoofed"] = spoofed # Should be a dictionary return analyzed_headers diff --git a/gpg-tools/1.0.0/src/app.py b/gpg-tools/1.0.0/src/app.py index d92b9e53..7db1c28b 100644 --- a/gpg-tools/1.0.0/src/app.py +++ b/gpg-tools/1.0.0/src/app.py @@ -79,9 +79,7 @@ def encrypt_file( ) ) - gpg = gnupg.GPG( - gnupghome=os.path.join("/app/local/", gpg_home), gpgbinary="/usr/bin/gpg" - ) + gpg = gnupg.GPG(gnupghome=os.path.join("/app/local/", gpg_home), gpgbinary="/usr/bin/gpg") with tempfile.NamedTemporaryFile(delete=False) as tmpfile: with open(tmpfile.name, "wb") as f: diff --git a/gpg-tools/README.md b/gpg-tools/README.md new file mode 100644 index 00000000..656b8769 --- /dev/null +++ b/gpg-tools/README.md @@ -0,0 +1,14 @@ +# GPG Tools +GPG tools is a utility app can help with encryption and decryption. It utilizes your private and public key, along with a password to do so, and requires a file as an input. + +## Authentication +Authentication for the app is necessary in order to decrypt or encrypt files or data. How this is handled from version 1.1.0 is through a Zip file with all your resources, uploaded to the Shuffle File storage. A prerequisite is already having a + +**Required Authentication Arguments:** +- File_ID: Points to the File ID of the Zip file containing your Private & Public key(s) +- Password: The password that protects your Private Key + +Getting the ZIP's File ID: +1. Create your public & Private key +2. `zip -r gpg.zip .` +3. Upload diff --git a/gpg-tools/gpgtest/gpg_run.py b/gpg-tools/gpgtest/gpg_run.py new file mode 100644 index 00000000..866f0db2 --- /dev/null +++ b/gpg-tools/gpgtest/gpg_run.py @@ -0,0 +1,105 @@ +import tempfile +import requests +import zipfile +import gnupg +import os + +file_id = "" + +def get_file(value): + url = "https://shuffler.io" + authorization = "" + full_execution = { + "execution_id": "1234", + "authorization": "", + "workflow": { + "execution_org": { + "id": "", + } + } + } + + full_execution = full_execution + org_id = full_execution["workflow"]["execution_org"]["id"] + + #logger.info("SHOULD GET FILES BASED ON ORG %s, workflow %s and value(s) %s" % (org_id, full_execution["workflow"]["id"], value)) + + if isinstance(value, list): + pass + #self.logger.info("IS LIST!") + #if len(value) == 1: + # value = value[0] + else: + value = [value] + + returns = [] + for item in value: + #self.logger.info("VALUE: %s" % item) + if len(item) != 36 and not item.startswith("file_"): + #self.logger.info("Bad length for file value %s" % item) + continue + #return { + # "filename": "", + # "data": "", + # "success": False, + #} + + get_path = "/api/v1/files/%s?execution_id=%s" % (item, full_execution["execution_id"]) + headers = { + "Content-Type": "application/json", + "Authorization": "Bearer %s" % authorization + } + + ret1 = requests.get("%s%s" % (url, get_path), headers=headers) + if ret1.status_code != 200: + returns.append({ + "filename": "", + "data": "", + "success": False, + }) + continue + + content_path = "/api/v1/files/%s/content?execution_id=%s" % (item, full_execution["execution_id"]) + ret2 = requests.get("%s%s" % (url, content_path), headers=headers) + if ret2.status_code == 200: + tmpdata = ret1.json() + returndata = { + "success": True, + "filename": tmpdata["filename"], + "data": ret2.content, + } + returns.append(returndata) + + + if len(returns) == 0: + return { + "success": False, + "filename": "", + "data": b"", + } + elif len(returns) == 1: + return returns[0] + else: + return returns + +def get_auth(file_id): + item = get_file(file_id) + tmpdirname = "/tmp/%s" % file_id + if not os.path.exists(tmpdirname): + os.mkdir(tmpdirname) + + #with tempfile.TemporaryDirectory() as tmpdirname: + # Get archive and save phisically + with open(os.path.join(tmpdirname, "archive"), "wb") as f: + f.write(item["data"]) + + # Grab files before, upload them later + with zipfile.ZipFile(os.path.join(tmpdirname, "archive")) as z_file: + print("Past zip extraction") + for member in z_file.namelist(): + z_file.extract(member, tmpdirname) + + gpg = gnupg.GPG(gnupghome=tmpdirname) + return gpg + +get_auth(file_id) diff --git a/microsoft-identity-and-access/1.0.0/src/app.py b/microsoft-identity-and-access/1.0.0/src/app.py index 9eb5b21e..07a791ad 100644 --- a/microsoft-identity-and-access/1.0.0/src/app.py +++ b/microsoft-identity-and-access/1.0.0/src/app.py @@ -304,17 +304,17 @@ def remove_administrative_unit_member(self, tenant_id, client_id, client_secret, return {"success": False, "reason": "Bad status code %d - expecting 200." % ret.status_code,"error_response":ret.text} - def list_risky_users(self, tenant_id, client_id, client_secret, amount=20, skip=0): + def list_risky_users(self, tenant_id, client_id, client_secret, amount=50, skip=0): graph_url = "https://graph.microsoft.com" session = self.authenticate(tenant_id, client_id, client_secret, graph_url) if amount == 0 or amount == "": - amount = 20 + amount = 50 if skip == 0 or skip == "": skip = 0 #graph_url = f"https://graph.microsoft.com/v1.0/identityProtection/riskyUsers?$top=%d&$skip=%d" % (int(amount), int(skip)) - graph_url = f"https://graph.microsoft.com/v1.0/identityProtection/riskyUsers" + graph_url = f"https://graph.microsoft.com/v1.0/identityProtection/riskyUsers?$top=%d" % (int(amount)) ret = session.get(graph_url) print(ret.status_code) print(ret.text) diff --git a/shuffle-tools/1.2.0/api.yaml b/shuffle-tools/1.2.0/api.yaml index 3c3a4bbd..21a4085f 100644 --- a/shuffle-tools/1.2.0/api.yaml +++ b/shuffle-tools/1.2.0/api.yaml @@ -552,7 +552,7 @@ actions: schema: type: string - name: xml_json_convertor - description: Converts xml to json and vice versa + description: Converts xml or html to json and vice versa. parameters: - name: convertto required: true diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 5ec5bd0c..25b9d933 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -605,7 +605,7 @@ def filter_list(self, input_list, field, check, value, opposite): if not isinstance(input_list, list): return { "success": False, - "reason": "Error: input isnt a list. Remove # to use this action.", + "reason": "Error: input isnt a list. Please use conditions instead if using JSON.", "valid": [], "invalid": [], } From 79ca508ca22456af78817dee93a43edda9d7f212 Mon Sep 17 00:00:00 2001 From: frikky Date: Thu, 23 Mar 2023 14:47:30 +0100 Subject: [PATCH 013/259] Fixed user input replies --- shuffle-subflow/1.1.0/src/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shuffle-subflow/1.1.0/src/app.py b/shuffle-subflow/1.1.0/src/app.py index 7c0a609c..b6197df4 100644 --- a/shuffle-subflow/1.1.0/src/app.py +++ b/shuffle-subflow/1.1.0/src/app.py @@ -75,9 +75,9 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" "information": information, "parent_workflow": self.full_execution["workflow"]["id"], "frontend_continue": "%s/workflows/%s/run?authorization=%s&reference_execution=%s&answer=true" % (frontend_url, self.full_execution["workflow"]["id"], user_apikey, self.full_execution["execution_id"]), - "frontend_abort": "%s/workflows/%s/run?authorization=%s&reference_execution=%s&answer=true" % (frontend_url, self.full_execution["workflow"]["id"], user_apikey, self.full_execution["execution_id"]), + "frontend_abort": "%s/workflows/%s/run?authorization=%s&reference_execution=%s&answer=false" % (frontend_url, self.full_execution["workflow"]["id"], user_apikey, self.full_execution["execution_id"]), "api_continue": "%s/api/v1/workflows/%s/execute?authorization=%s&reference_execution=%s&answer=true" % (frontend_url, self.full_execution["workflow"]["id"], user_apikey, self.full_execution["execution_id"]), - "api_abort": "%s/api/v1/workflows/%s/execute?authorization=%s&reference_execution=%s&answer=true" % (frontend_url, self.full_execution["workflow"]["id"], user_apikey, self.full_execution["execution_id"]), + "api_abort": "%s/api/v1/workflows/%s/execute?authorization=%s&reference_execution=%s&answer=false" % (frontend_url, self.full_execution["workflow"]["id"], user_apikey, self.full_execution["execution_id"]), }) ret = self.run_subflow(user_apikey, item, argument, source_workflow=self.full_execution["workflow"]["id"], source_execution=self.full_execution["execution_id"], source_auth=self.full_execution["authorization"], startnode=startnode, backend_url=backend_url, source_node=source_node) From aa7dd9195173a0c05e170b2ca76709fff4fd90df Mon Sep 17 00:00:00 2001 From: frikky Date: Thu, 23 Mar 2023 16:15:34 +0100 Subject: [PATCH 014/259] Auth fix for gpg --- gpg-tools/gpgtest/gpg_run.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/gpg-tools/gpgtest/gpg_run.py b/gpg-tools/gpgtest/gpg_run.py index 866f0db2..63eb7db6 100644 --- a/gpg-tools/gpgtest/gpg_run.py +++ b/gpg-tools/gpgtest/gpg_run.py @@ -4,11 +4,11 @@ import gnupg import os -file_id = "" +file_id = "file_b2586a47-19fb-449c-9f2b-d24fa16d874d" def get_file(value): url = "https://shuffler.io" - authorization = "" + authorization = "fdf4da42-65d2-4fa5-8a22-8ed25c60ff0d" full_execution = { "execution_id": "1234", "authorization": "", @@ -94,12 +94,26 @@ def get_auth(file_id): f.write(item["data"]) # Grab files before, upload them later + gpgfound = False with zipfile.ZipFile(os.path.join(tmpdirname, "archive")) as z_file: print("Past zip extraction") for member in z_file.namelist(): + print(member) + if member == ".gpg": + gpgfound = True + z_file.extract(member, tmpdirname) - gpg = gnupg.GPG(gnupghome=tmpdirname) + os.remove(os.path.join(tmpdirname, "archive")) + + if gpgfound: + tmpdirname = os.path.join(tmpdirname, ".gpg") + + try: + gpg = gnupg.GPG(gnupghome=tmpdirname) + except TypeError: + gpg = gnupg.GPG(homedir=tmpdirname) + return gpg get_auth(file_id) From b61fba8c1d26c471f2586369dcf636d357f8b402 Mon Sep 17 00:00:00 2001 From: frikky Date: Thu, 23 Mar 2023 16:18:13 +0100 Subject: [PATCH 015/259] Removed apikeys and such and reset them --- gpg-tools/gpgtest/gpg_run.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gpg-tools/gpgtest/gpg_run.py b/gpg-tools/gpgtest/gpg_run.py index 63eb7db6..33818fba 100644 --- a/gpg-tools/gpgtest/gpg_run.py +++ b/gpg-tools/gpgtest/gpg_run.py @@ -4,11 +4,11 @@ import gnupg import os -file_id = "file_b2586a47-19fb-449c-9f2b-d24fa16d874d" +file_id = "" def get_file(value): url = "https://shuffler.io" - authorization = "fdf4da42-65d2-4fa5-8a22-8ed25c60ff0d" + authorization = "" full_execution = { "execution_id": "1234", "authorization": "", From ab213815096bc716ad416301088c3e161ce41b3c Mon Sep 17 00:00:00 2001 From: frikky Date: Thu, 23 Mar 2023 16:25:12 +0100 Subject: [PATCH 016/259] Added cleanup stuff --- gpg-tools/gpgtest/gpg_run.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/gpg-tools/gpgtest/gpg_run.py b/gpg-tools/gpgtest/gpg_run.py index 33818fba..bffc10b6 100644 --- a/gpg-tools/gpgtest/gpg_run.py +++ b/gpg-tools/gpgtest/gpg_run.py @@ -82,14 +82,21 @@ def get_file(value): else: return returns +def cleanup(file_id): + tmpdirname = f"/tmp/{file_id}" + if os.path.exists(tmpdirname): + os.remove(tmpdirname) + def get_auth(file_id): item = get_file(file_id) - tmpdirname = "/tmp/%s" % file_id - if not os.path.exists(tmpdirname): - os.mkdir(tmpdirname) + tmpdirname = f"/tmp/{file_id}" + + # Clean up all old stuff + if os.path.exists(tmpdirname): + os.remove(tmpdirname) - #with tempfile.TemporaryDirectory() as tmpdirname: # Get archive and save phisically + os.mkdir(tmpdirname) with open(os.path.join(tmpdirname, "archive"), "wb") as f: f.write(item["data"]) From c062bd84a7b472ca80f4630a7fd3069a4844f2c2 Mon Sep 17 00:00:00 2001 From: deb-alex Date: Fri, 24 Mar 2023 11:34:54 +0100 Subject: [PATCH 017/259] GpgTools v1.1.0 --- gpg-tools/1.1.0/Dockerfile | 26 +++ gpg-tools/1.1.0/README.md | 18 ++ gpg-tools/1.1.0/api.yaml | 124 +++++++++++++ gpg-tools/1.1.0/docker-compose.yml | 20 ++ gpg-tools/1.1.0/requirements.txt | 2 + gpg-tools/1.1.0/src/app.py | 285 +++++++++++++++++++++++++++++ gpg-tools/README.md | 14 -- 7 files changed, 475 insertions(+), 14 deletions(-) create mode 100644 gpg-tools/1.1.0/Dockerfile create mode 100644 gpg-tools/1.1.0/README.md create mode 100644 gpg-tools/1.1.0/api.yaml create mode 100644 gpg-tools/1.1.0/docker-compose.yml create mode 100644 gpg-tools/1.1.0/requirements.txt create mode 100644 gpg-tools/1.1.0/src/app.py delete mode 100644 gpg-tools/README.md diff --git a/gpg-tools/1.1.0/Dockerfile b/gpg-tools/1.1.0/Dockerfile new file mode 100644 index 00000000..370fb0d4 --- /dev/null +++ b/gpg-tools/1.1.0/Dockerfile @@ -0,0 +1,26 @@ +# Base our app image off of the WALKOFF App SDK image +FROM frikky/shuffle:app_sdk as base + +# We're going to stage away all of the bloat from the build tools so lets create a builder stage +FROM base as builder + +# Install all alpine build tools needed for our pip installs +RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev + +# Install all of our pip packages in a single directory that we can copy to our base image later +RUN mkdir /install +WORKDIR /install +COPY requirements.txt /requirements.txt +RUN pip install --prefix="/install" -r /requirements.txt + +# Switch back to our base image and copy in all of our built packages and source code +FROM base +COPY --from=builder /install /usr/local +COPY src /app + +# Install any binary dependencies needed in our final image +RUN apk --no-cache add --update gnupg + +# Finally, lets run our app! +WORKDIR /app +CMD python app.py --log-level DEBUG diff --git a/gpg-tools/1.1.0/README.md b/gpg-tools/1.1.0/README.md new file mode 100644 index 00000000..a4625897 --- /dev/null +++ b/gpg-tools/1.1.0/README.md @@ -0,0 +1,18 @@ +# GPG Tools +GPG tools is a utility app can help with encryption and decryption of text and files. +It requires your own GPG keystore containing private and public keys, along with the password to access the keystore. + +## Authentication +Authentication for the app is necessary in order to decrypt or encrypt files or data. +How this is handled from version 1.1.0 is through a Zip file with all your resources, uploaded to the Shuffle File storage. +The ZIP archive must contain the entire GnuPG Home Directory, named '.gnupg' + +**Required Authentication Arguments:** +- Zip_File_ID: Points to the File ID of the Zip file containing your Private & Public key(s) +- Password: The password that protects your Private Key + +Getting the ZIP's File ID: +1. Create your public & Private key with `gpg --full-gen-key` +2. A GPG Home Dir is created, under `~/.gnupg` +2. Compress the GPH Home Dir `zip -r gpg.zip .gnupg/` +3. Upload the ZIP file `gpg.zip` to Shuffle Files and obtain the FileID for the Zip file. \ No newline at end of file diff --git a/gpg-tools/1.1.0/api.yaml b/gpg-tools/1.1.0/api.yaml new file mode 100644 index 00000000..1a1ba296 --- /dev/null +++ b/gpg-tools/1.1.0/api.yaml @@ -0,0 +1,124 @@ +app_version: 1.1.0 +name: Gpg Tools +description: A gpg app for Shuffle +contact_info: + name: "@deb-alex" +authentication: + required: true + parameters: + - name: zip_file_id + description: FileID for the ZIP file containing the GNUPG home directory + required: true + multiline: false + schema: + type: string + - name: password + description: Password to use for key store decryption + required: true + multiline: false + schema: + type: string +tags: + - Encryption +categories: + - Encryption +actions: + - name: encrypt_text + description: Encrypt text with gpg + parameters: + - name: clear_text + description: Clear text to encrypt + required: true + multiline: false + schema: + type: string + - name: recipients + description: List of key fingerprints separated by comma (,) + required: true + multiline: false + schema: + type: string + - name: always_trust + description: Skip key validation and assume that used keys are always fully trusted. + required: true + options: + - "false" + - "true" + schema: + type: bool + + - name: decrypt_text + description: Decrypt text with gpg + parameters: + - name: encrypted_text + description: Encrypted text message to decrypt + required: true + multiline: false + schema: + type: string + - name: always_trust + description: Skip key validation and assume that used keys are always fully trusted. + required: true + options: + - "false" + - "true" + schema: + type: bool + + - name: encrypt_file + description: Encrypt file with gpg + parameters: + - name: file_id + description: FileID of the clear text file to encrypt + required: true + multiline: false + schema: + type: file + - name: output_name + description: Name of the encrypted output file + required: true + multiline: false + schema: + type: string + - name: recipients + description: List of key fingerprints separated by comma (,) + required: true + multiline: false + schema: + type: string + - name: always_trust + description: Skip key validation and assume that used keys are always fully trusted. + required: true + options: + - "false" + - "true" + schema: + type: bool + - name: decrypt_file + description: Decrypt file with gpg + parameters: + - name: file_id + description: FileID of the encrypted file to decrypt + required: true + multiline: false + schema: + type: file + - name: output_name + description: Name of the decrypted output file + required: true + multiline: false + schema: + type: string + - name: always_trust + description: Skip key validation and assume that used keys are always fully trusted. + required: true + options: + - "false" + - "true" + schema: + type: bool + + returns: + schema: + type: string +large_image: data:image/png;base64,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 diff --git a/gpg-tools/1.1.0/docker-compose.yml b/gpg-tools/1.1.0/docker-compose.yml new file mode 100644 index 00000000..02b32361 --- /dev/null +++ b/gpg-tools/1.1.0/docker-compose.yml @@ -0,0 +1,20 @@ +version: "3.4" +services: + hello_world: + build: + context: . + dockerfile: Dockerfile + # image: walkoff_registry:5000/walkoff_app_HelloWorld-v1-0 + deploy: + mode: replicated + replicas: 10 + restart_policy: + condition: none + restart: "no" + secrets: + - secret1 +# secrets: +# secret1: +# file: ./secret_data +# labels: +# foo: bar diff --git a/gpg-tools/1.1.0/requirements.txt b/gpg-tools/1.1.0/requirements.txt new file mode 100644 index 00000000..e5ff88aa --- /dev/null +++ b/gpg-tools/1.1.0/requirements.txt @@ -0,0 +1,2 @@ +python-gnupg==0.4.6 +requests==2.25.1 \ No newline at end of file diff --git a/gpg-tools/1.1.0/src/app.py b/gpg-tools/1.1.0/src/app.py new file mode 100644 index 00000000..e8932462 --- /dev/null +++ b/gpg-tools/1.1.0/src/app.py @@ -0,0 +1,285 @@ +import os +import socket +import asyncio +import time +import random +import json +import subprocess +import requests +import tempfile +import gnupg +import zipfile +import shutil + + +from walkoff_app_sdk.app_base import AppBase + + +class Gpg(AppBase): + """ + An example of a Walkoff App. + Inherit from the AppBase class to have Redis, logging, and console logging set up behind the scenes. + """ + + __version__ = "1.1.0" + app_name = "Gpg Tools" + + def __init__(self, redis, logger, console_logger=None): + """ + Each app should have this __init__ to set up Redis and logging. + :param redis: + :param logger: + :param console_logger: + """ + super().__init__(redis, logger, console_logger) + + def extract_archive(self, zip_file_id, fileformat="zip", password=None): + try: + return_data = {"success": False, "files": []} + to_be_uploaded = [] + item = self.get_file(zip_file_id) + return_ids = None + + self.logger.info("Working with fileformat %s" % fileformat) + with tempfile.TemporaryDirectory() as tmpdirname: + + # Get archive and save phisically + with open(os.path.join(tmpdirname, "archive"), "wb") as f: + f.write(item["data"]) + + # Grab files before, upload them later + + # Zipfile for zipped archive + if fileformat.strip().lower() == "zip": + try: + self.logger.info("Starting zip extraction") + with zipfile.ZipFile(os.path.join(tmpdirname, "archive")) as z_file: + if password: + self.logger.info("In zip extraction with password") + z_file.setpassword(bytes(password.encode())) + + self.logger.info("Past zip extraction") + for member in z_file.namelist(): + filename = os.path.basename(member) + if not filename: + continue + + source = z_file.open(member) + to_be_uploaded.append( + {"filename": source.name.split("/")[-1], "data": source.read()} + ) + + return_data["success"] = True + except (zipfile.BadZipFile, Exception): + return_data["files"].append( + { + "success": False, + "file_id": zip_file_id, + "filename": item["filename"], + "message": "File is not a valid zip archive", + } + ) + else: + return "No such format: %s" % fileformat + + self.logger.info("Breaking as this only handles one archive at a time.") + if len(to_be_uploaded) > 0: + return_ids = self.set_files(to_be_uploaded) + self.logger.info(f"Got return ids from files: {return_ids}") + + for i in range(len(return_ids)): + return_data["archive_id"] = zip_file_id + try: + return_data["files"].append( + { + "success": True, + "file_id": return_ids[i], + "filename": to_be_uploaded[i]["filename"], + } + ) + except: + return_data["files"].append( + { + "success": True, + "file_id": return_ids[i], + } + ) + else: + self.logger.info(f"No file ids to upload.") + return_data["success"] = False + return_data["files"].append( + { + "success": False, + "filename": "No data in archive", + "message": "Archive is empty", + } + ) + + return return_data + + except Exception as excp: + return {"success": False, "message": "%s" % excp} + + def get_auth(self, file_id): + item = self.get_file(file_id) + tmpdirname = f"/tmp/{file_id}" + + # Clean up all old stuff + if os.path.exists(tmpdirname): + shutil.rmtree(tmpdirname, ) + + # Get archive and save physically + os.mkdir(tmpdirname) + with open(os.path.join(tmpdirname, "archive"), "wb") as f: + f.write(item["data"]) + + # Grab files before, upload them later + gpgfound = False + with zipfile.ZipFile(os.path.join(tmpdirname, "archive")) as z_file: + print("Past zip extraction") + for member in z_file.namelist(): + print(member) + if member == ".gnupg/": + gpgfound = True + + z_file.extract(member, tmpdirname) + + os.remove(os.path.join(tmpdirname, "archive")) + + if gpgfound: + tmpdirname = os.path.join(tmpdirname, ".gnupg") + + try: + gpg = gnupg.GPG(gnupghome=tmpdirname) + except TypeError: + gpg = gnupg.GPG(homedir=tmpdirname) + + return gpg + + def cleanup(self, zip_file_id): + + tmpdirname = f"/tmp/{zip_file_id}" + + if os.path.exists(tmpdirname): + shutil.rmtree(tmpdirname) + self.logger.debug(">> Cleanup complete") + + return + + + def decrypt_text( + self, zip_file_id, encrypted_text, password, always_trust + ): + gpg = self.get_auth(zip_file_id) + self.logger.debug(">> Created GPG instance") + + decrypted_text = gpg.decrypt( + encrypted_text, + passphrase=password, + always_trust=always_trust + ) + + # Delete the downloaded keystore + self.cleanup(zip_file_id) + + if decrypted_text.ok: + return {"success": True, "data": decrypted_text.data.decode('utf-8')} + else: + return {"success": False, "error": decrypted_text.stderr } + + + + def encrypt_text( + self, zip_file_id, clear_text, recipients, always_trust + ): + gpg = self.get_auth(zip_file_id) + self.logger.debug(">> Created GPG instance") + + # Build list of recipients from comma-separated string + recipients = recipients.split(',') + + self.logger.debug(f">> Recipients: {recipients}") + + encrypted_text = gpg.encrypt( + clear_text, + recipients=recipients, + always_trust=always_trust + ) + + # Delete the downloaded keystore + self.cleanup(zip_file_id) + + if encrypted_text.ok: + return {"success": True, "data": encrypted_text.data.decode('utf-8')} + else: + return {"success": False, "error": encrypted_text.stderr } + + + def decrypt_file( + self, zip_file_id, password, file_id, output_name, always_trust + ): + gpg = self.get_auth(zip_file_id) + self.logger.debug(">> Created GPG instance") + + if file_id["success"] == False: + return "Error managing files." + + always_trust = True if always_trust.lower() == "true" else False + + ret_decrypt = gpg.decrypt( + file_id["data"], + passphrase=password, + always_trust=always_trust, + ) + + # Delete the downloaded keystore + self.cleanup(zip_file_id) + + if ret_decrypt.ok: + self.logger.debug(">> File decrypted") + + file_id = self.set_files([{"filename": output_name, "data": ret_decrypt.data}]) + if len(file_id) == 1: + file_id = file_id[0] + return {"success": True, "id": file_id} + else: + return {"success": False, "error": ret_decrypt.stderr} + + def encrypt_file( + self, zip_file_id, file_id, output_name, recipients, always_trust + ): + gpg = self.get_auth(zip_file_id) + self.logger.debug(">> Created GPG instance") + + if file_id["success"] == False: + return "Error managing files." + + always_trust = True if always_trust.lower() == "true" else False + + # Build list of recipients from comma-separated string + recipients = recipients.split(',') + + self.logger.debug(f">> Recipients: {recipients}") + + ret_encrypt = gpg.encrypt( + file_id['data'], + recipients=recipients, + always_trust=always_trust + ) + + # Delete the downloaded keystore + self.cleanup(zip_file_id) + + if ret_encrypt.ok: + self.logger.debug(">> File encrypted") + + file_id = self.set_files([{"filename": output_name, "data": ret_encrypt.data}]) + if len(file_id) == 1: + file_id = file_id[0] + return {"success": True, "id": file_id} + else: + return {"success": False, "error": ret_encrypt.stderr} + + +if __name__ == "__main__": + Gpg.run() diff --git a/gpg-tools/README.md b/gpg-tools/README.md deleted file mode 100644 index 656b8769..00000000 --- a/gpg-tools/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# GPG Tools -GPG tools is a utility app can help with encryption and decryption. It utilizes your private and public key, along with a password to do so, and requires a file as an input. - -## Authentication -Authentication for the app is necessary in order to decrypt or encrypt files or data. How this is handled from version 1.1.0 is through a Zip file with all your resources, uploaded to the Shuffle File storage. A prerequisite is already having a - -**Required Authentication Arguments:** -- File_ID: Points to the File ID of the Zip file containing your Private & Public key(s) -- Password: The password that protects your Private Key - -Getting the ZIP's File ID: -1. Create your public & Private key -2. `zip -r gpg.zip .` -3. Upload From 1a13d296a5f2f09cd8997ca85e97a011bb93ae8e Mon Sep 17 00:00:00 2001 From: deb-alex Date: Fri, 24 Mar 2023 11:38:16 +0100 Subject: [PATCH 018/259] Removed gpgtest --- gpg-tools/{1.1.0 => }/README.md | 0 gpg-tools/gpgtest/gpg_run.py | 126 -------------------------------- 2 files changed, 126 deletions(-) rename gpg-tools/{1.1.0 => }/README.md (100%) delete mode 100644 gpg-tools/gpgtest/gpg_run.py diff --git a/gpg-tools/1.1.0/README.md b/gpg-tools/README.md similarity index 100% rename from gpg-tools/1.1.0/README.md rename to gpg-tools/README.md diff --git a/gpg-tools/gpgtest/gpg_run.py b/gpg-tools/gpgtest/gpg_run.py deleted file mode 100644 index bffc10b6..00000000 --- a/gpg-tools/gpgtest/gpg_run.py +++ /dev/null @@ -1,126 +0,0 @@ -import tempfile -import requests -import zipfile -import gnupg -import os - -file_id = "" - -def get_file(value): - url = "https://shuffler.io" - authorization = "" - full_execution = { - "execution_id": "1234", - "authorization": "", - "workflow": { - "execution_org": { - "id": "", - } - } - } - - full_execution = full_execution - org_id = full_execution["workflow"]["execution_org"]["id"] - - #logger.info("SHOULD GET FILES BASED ON ORG %s, workflow %s and value(s) %s" % (org_id, full_execution["workflow"]["id"], value)) - - if isinstance(value, list): - pass - #self.logger.info("IS LIST!") - #if len(value) == 1: - # value = value[0] - else: - value = [value] - - returns = [] - for item in value: - #self.logger.info("VALUE: %s" % item) - if len(item) != 36 and not item.startswith("file_"): - #self.logger.info("Bad length for file value %s" % item) - continue - #return { - # "filename": "", - # "data": "", - # "success": False, - #} - - get_path = "/api/v1/files/%s?execution_id=%s" % (item, full_execution["execution_id"]) - headers = { - "Content-Type": "application/json", - "Authorization": "Bearer %s" % authorization - } - - ret1 = requests.get("%s%s" % (url, get_path), headers=headers) - if ret1.status_code != 200: - returns.append({ - "filename": "", - "data": "", - "success": False, - }) - continue - - content_path = "/api/v1/files/%s/content?execution_id=%s" % (item, full_execution["execution_id"]) - ret2 = requests.get("%s%s" % (url, content_path), headers=headers) - if ret2.status_code == 200: - tmpdata = ret1.json() - returndata = { - "success": True, - "filename": tmpdata["filename"], - "data": ret2.content, - } - returns.append(returndata) - - - if len(returns) == 0: - return { - "success": False, - "filename": "", - "data": b"", - } - elif len(returns) == 1: - return returns[0] - else: - return returns - -def cleanup(file_id): - tmpdirname = f"/tmp/{file_id}" - if os.path.exists(tmpdirname): - os.remove(tmpdirname) - -def get_auth(file_id): - item = get_file(file_id) - tmpdirname = f"/tmp/{file_id}" - - # Clean up all old stuff - if os.path.exists(tmpdirname): - os.remove(tmpdirname) - - # Get archive and save phisically - os.mkdir(tmpdirname) - with open(os.path.join(tmpdirname, "archive"), "wb") as f: - f.write(item["data"]) - - # Grab files before, upload them later - gpgfound = False - with zipfile.ZipFile(os.path.join(tmpdirname, "archive")) as z_file: - print("Past zip extraction") - for member in z_file.namelist(): - print(member) - if member == ".gpg": - gpgfound = True - - z_file.extract(member, tmpdirname) - - os.remove(os.path.join(tmpdirname, "archive")) - - if gpgfound: - tmpdirname = os.path.join(tmpdirname, ".gpg") - - try: - gpg = gnupg.GPG(gnupghome=tmpdirname) - except TypeError: - gpg = gnupg.GPG(homedir=tmpdirname) - - return gpg - -get_auth(file_id) From 729fc8a48f285d54b9e57ea33972d4101cd2b771 Mon Sep 17 00:00:00 2001 From: deb-alex Date: Fri, 24 Mar 2023 11:49:51 +0100 Subject: [PATCH 019/259] Fixed doc --- gpg-tools/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gpg-tools/README.md b/gpg-tools/README.md index a4625897..16d42d28 100644 --- a/gpg-tools/README.md +++ b/gpg-tools/README.md @@ -14,5 +14,5 @@ The ZIP archive must contain the entire GnuPG Home Directory, named '.gnupg' Getting the ZIP's File ID: 1. Create your public & Private key with `gpg --full-gen-key` 2. A GPG Home Dir is created, under `~/.gnupg` -2. Compress the GPH Home Dir `zip -r gpg.zip .gnupg/` -3. Upload the ZIP file `gpg.zip` to Shuffle Files and obtain the FileID for the Zip file. \ No newline at end of file +3. Compress the GPH Home Dir `zip -r gpg.zip .gnupg/` +4. Upload the ZIP file `gpg.zip` to Shuffle Files and obtain the FileID for the Zip file. \ No newline at end of file From 5c7e8e097116407058d695a545b193adbe0ff768 Mon Sep 17 00:00:00 2001 From: frikky Date: Thu, 13 Apr 2023 15:05:08 +0200 Subject: [PATCH 020/259] Added fallback to base_url in subflow --- shuffle-subflow/1.0.0/src/app.py | 2 ++ shuffle-subflow/1.1.0/src/app.py | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/shuffle-subflow/1.0.0/src/app.py b/shuffle-subflow/1.0.0/src/app.py index 68ff6ba9..e57fd7ef 100644 --- a/shuffle-subflow/1.0.0/src/app.py +++ b/shuffle-subflow/1.0.0/src/app.py @@ -119,6 +119,8 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" def run_subflow(self, user_apikey, workflow, argument, source_workflow="", source_execution="", source_node="", source_auth="", startnode="", backend_url=""): #print("STARTNODE: %s" % startnode) url = "%s/api/v1/workflows/%s/execute" % (self.url, workflow) + if len(self.base_url) > 0: + url = "%s/api/v1/workflows/%s/execute" % (self.base_url, workflow) params = {} if len(str(source_workflow)) > 0: diff --git a/shuffle-subflow/1.1.0/src/app.py b/shuffle-subflow/1.1.0/src/app.py index b6197df4..926912e8 100644 --- a/shuffle-subflow/1.1.0/src/app.py +++ b/shuffle-subflow/1.1.0/src/app.py @@ -44,8 +44,11 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" } url = self.url + if len(self.base_url) > 0: + url = self.base_url + if len(str(backend_url)) > 0: - url = "%s" % (backend_url) + url = backend_url print("Found backend url: %s" % url) #if len(information): @@ -140,6 +143,8 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" def run_subflow(self, user_apikey, workflow, argument, source_workflow="", source_execution="", source_node="", source_auth="", startnode="", backend_url=""): #print("STARTNODE: %s" % startnode) url = "%s/api/v1/workflows/%s/execute" % (self.url, workflow) + if len(self.base_url) > 0: + url = "%s/api/v1/workflows/%s/execute" % (self.base_url, workflow) params = {} if len(str(source_workflow)) > 0: From 152487f18870bcb54996c3b1d838afa02073e8d9 Mon Sep 17 00:00:00 2001 From: dhaval055 Date: Fri, 14 Apr 2023 15:25:14 +0530 Subject: [PATCH 021/259] Added run SSH command to shuffle-tools --- shuffle-tools/1.2.0/api.yaml | 31 +++++++++++++++++++++++++++++++ shuffle-tools/1.2.0/src/app.py | 26 ++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/shuffle-tools/1.2.0/api.yaml b/shuffle-tools/1.2.0/api.yaml index 21a4085f..b76c5a74 100644 --- a/shuffle-tools/1.2.0/api.yaml +++ b/shuffle-tools/1.2.0/api.yaml @@ -1066,6 +1066,37 @@ actions: - false schema: type: string + - name: run_ssh_command + description: 'Run a command on remote machine with SSH' + parameters: + - name: host + description: Host IP + required: true + multiline: false + example: '192.168.55.11' + schema: + type: string + - name: user_name + description: User on remote system + required: true + multiline: false + example: 'root' + schema: + type: string + - name: private_key_file_id + description: Private key file ID + required: true + multiline: false + example: 'file_c5c87a50-4146-40e2-a698-78cf13bf65c0' + schema: + type: string + - name: command + description: Command you want to run + required: true + multiline: true + example: 'ls -la' + schema: + type: string large_image: data:image/png;base64,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 # yamllint disable-line rule:line-length diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 25b9d933..ebb4cf7a 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -2331,8 +2331,34 @@ def generate_random_string(length=16, special_characters=True): "success": True, "password": password, } + + def run_ssh_command(self, host, user_name, private_key_file_id, command): + new_file = self.get_file(private_key_file_id) + + ssh_client = paramiko.SSHClient() + ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + + try: + key_data = new_file["data"].decode() + except Exception as e: + return {"success":"false","message":str(e)} + private_key_file = io.StringIO() + private_key_file.write(key_data) + private_key_file.seek(0) + private_key = paramiko.RSAKey.from_private_key(private_key_file) + + try: + ssh_client.connect(hostname=host, username=user_name, pkey= private_key) + except Exception as e: + return {"success":"false","message":str(e)} + + try: + stdin, stdout, stderr = ssh_client.exec_command(str(command)) + except Exception as e: + return {"success":"false","message":str(e)} + return {"success":"true","output": stdout.read().decode()} if __name__ == "__main__": From cccc36748feaaef5b7c5435437526da8fa704bd7 Mon Sep 17 00:00:00 2001 From: dhaval055 Date: Wed, 26 Apr 2023 18:02:02 +0530 Subject: [PATCH 022/259] added actions to change password,lock, unlock user in ad --- active-directory/1.1.0/Dockerfile | 26 ++ active-directory/1.1.0/README.md | 42 +++ active-directory/1.1.0/api.yaml | 246 ++++++++++++++ active-directory/1.1.0/requirements.txt | 2 + active-directory/1.1.0/src/app.py | 422 ++++++++++++++++++++++++ active-directory/1.1.0/src/sample.py | 197 +++++++++++ 6 files changed, 935 insertions(+) create mode 100644 active-directory/1.1.0/Dockerfile create mode 100644 active-directory/1.1.0/README.md create mode 100644 active-directory/1.1.0/api.yaml create mode 100644 active-directory/1.1.0/requirements.txt create mode 100644 active-directory/1.1.0/src/app.py create mode 100644 active-directory/1.1.0/src/sample.py diff --git a/active-directory/1.1.0/Dockerfile b/active-directory/1.1.0/Dockerfile new file mode 100644 index 00000000..364e1531 --- /dev/null +++ b/active-directory/1.1.0/Dockerfile @@ -0,0 +1,26 @@ +# Base our app image off of the WALKOFF App SDK image +FROM frikky/shuffle:app_sdk as base + +# We're going to stage away all of the bloat from the build tools so lets create a builder stage +FROM base as builder + +# Install all alpine build tools needed for our pip installs +RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev + +# Install all of our pip packages in a single directory that we can copy to our base image later +RUN mkdir /install +WORKDIR /install +COPY requirements.txt /requirements.txt +RUN pip install --prefix="/install" -r /requirements.txt + +# Switch back to our base image and copy in all of our built packages and source code +FROM base +COPY --from=builder /install /usr/local +COPY src /app + +# Install any binary dependencies needed in our final image +# RUN apk --no-cache add --update my_binary_dependency + +# Finally, lets run our app! +WORKDIR /app +CMD python app.py --log-level DEBUG diff --git a/active-directory/1.1.0/README.md b/active-directory/1.1.0/README.md new file mode 100644 index 00000000..e99000f2 --- /dev/null +++ b/active-directory/1.1.0/README.md @@ -0,0 +1,42 @@ +# Active Directory +Active Directory is used all over the world for different reasons. This app helps you explore and control those users. It's based on an LDAP connection. + +## Authentication +* server: The IP or hostname to connect to +* port: The port to connect to. Default: 389 +* domain: Your CORP domain. Used to login properly together with your login_user +* login_user: Your username. DONT add CORP\\ in front +* password: The password of the user logging in. +* base_dn: The base DN found by running `Get-ADDomain` in powershell, then getting the value of the field "UsersContainer". Should NOT contain spaces. example: `OU=Users,DC=icplahd,DC=com` +* use_ssl: Whether to use SSL to connect to the default port. + +* search_base: Usually same as base_dn + +## Base DN +Finding the Base DN can be done by going to a Windows server in the domain. + +1. Open Powershell +2. Run +``` +Get-ADDomain +``` +3. Find the response from "UsersContainer" and use this for Base DN and Search Base + +## Typical issues +- InvalidCredentials: This happens when the credentials are wrong. See #authentication to understand if your format for your username/password is correct. + +## Features +get user attributes -- done +reset password -- done +change password at next logon -- done +enable/disable user -- done + + +## Upcoming Features +add/remove users to group -- dev +get group attributes -- dev +get group members -- dev +get system attributes -- dev +set system attributes -- dev +change computer OU -- dev +Connect to LDAPs using certificates and TLS diff --git a/active-directory/1.1.0/api.yaml b/active-directory/1.1.0/api.yaml new file mode 100644 index 00000000..1dde1556 --- /dev/null +++ b/active-directory/1.1.0/api.yaml @@ -0,0 +1,246 @@ +app_version: 1.1.0 +name: Active Directory +description: Active Directory and LDAP/LDAPS. For full usage of the action configure using LDAPS. +contact_info: + name: "@d4rkw0lv3s" + url: https://github.com/D4rkw0lv3s + email: d4rkw0lv3s@outlook.pt +tags: + - activedirectory + - ldap + - ldaps + - Azure AD +categories: + - IAM + - assets +authentication: + required: true + parameters: + - name: server + description: "Server fqdn or ip address." + example: "server-1.mycompany.com or 127.0.0.1" + required: true + schema: + type: string + - name: domain + description: "Domain to BIND to AD/LDAP with. Should JUST be the NetBIOSName from Get-Addomain" + example: "ICPLAHD" + required: true + schema: + type: string + - name: login_user + description: "Username to BIND to AD/LDAP with" + example: "binduser" + required: true + schema: + type: string + - name: password + description: "Password to BIND with." + example: "Password1IsBad!" + required: true + schema: + type: string + - name: base_dn + description: "Search Base DN" + example: "OU=Users,DC=icplahd,DC=com" + required: true + schema: + type: string + - name: use_ssl + description: "Use SSL Connection Security" + required: true + example: "True" + options: + - "true" + - "false" + schema: + type: string +actions: + - name: user_attributes + description: Query AD for details about a specified user + parameters: + - name: samaccountname + description: user to query + required: true + multiline: false + example: 'user01' + schema: + type: string + - name: search_base + description: "If empty it will use the base_dn." + required: false + multiline: false + example: "OU=Users,DC=mycompany,DC=com" + schema: + type: string + returns: + schema: + type: string + - name: set_password + description: Set password for given user + parameters: + - name: samaccountname + description: user to query + required: true + multiline: false + example: 'user01' + schema: + type: string + - name: new_password + description: user new password + required: true + multiline: false + example: 'Password1IsBad!' + schema: + type: string + - name: repeat_password + description: repeat the new password + required: true + multiline: false + schema: + type: string + - name: search_base + description: "If empty it will use the base_dn." + required: false + multiline: false + example: "OU=Users,DC=mycompany,DC=com" + schema: + type: string + returns: + schema: + type: string + - name: change_password_at_next_logon + description: Force user to change password at next logon + parameters: + - name: samaccountname + description: user to query + required: true + multiline: false + example: 'user01' + schema: + type: string + - name: search_base + description: "If empty it will use the base_dn." + required: false + multiline: false + example: "OU=Users,DC=mycompany,DC=com" + schema: + type: string + returns: + schema: + type: string + - name: enable_user + description: Enable User account + parameters: + - name: samaccountname + description: user to query + required: true + multiline: false + example: 'user01' + schema: + type: string + - name: search_base + description: "If empty it will use the base_dn." + required: false + multiline: false + example: "OU=Users,DC=mycompany,DC=com" + schema: + type: string + returns: + schema: + type: string + - name: disable_user + description: Disable User account + parameters: + - name: samaccountname + description: user to query + required: true + multiline: false + example: 'user01' + schema: + type: string + - name: search_base + description: "If empty it will use the base_dn." + required: false + multiline: false + example: "OU=Users,DC=icplahd,DC=com" + schema: + type: string + returns: + schema: + type: string + - name: lock_user + description: Lock User account + parameters: + - name: samaccountname + description: user to lock + required: true + multiline: false + example: 'user01' + schema: + type: string + - name: search_base + description: "If empty it will use the base_dn." + required: false + multiline: false + example: "OU=Users,DC=icplahd,DC=com" + schema: + type: string + returns: + schema: + type: string + - name: unlock_user + description: Unlock User account + parameters: + - name: samaccountname + description: user to unlock + required: true + multiline: false + example: 'user01' + schema: + type: string + - name: search_base + description: "If empty it will use the base_dn." + required: false + multiline: false + example: "OU=Users,DC=icplahd,DC=com" + schema: + type: string + returns: + schema: + type: string + - name: change_user_password_at_next_login + description: Set given password for user at next login + parameters: + - name: samaccountname + description: user to change password for + required: true + multiline: false + example: 'user01' + schema: + type: string + - name: search_base + description: "If empty it will use the base_dn." + required: false + multiline: false + example: "OU=Users,DC=icplahd,DC=com" + schema: + type: string + - name: new_user_password + description: "New password you want to set" + required: true + multiline: false + example: "***" + schema: + type: string + - name: repeat_new_user_password + description: "Repeat new password you want to set" + required: true + multiline: false + example: "***" + schema: + type: string + returns: + schema: + type: string +large_image: data:image/png;base64,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 diff --git a/active-directory/1.1.0/requirements.txt b/active-directory/1.1.0/requirements.txt new file mode 100644 index 00000000..5238833e --- /dev/null +++ b/active-directory/1.1.0/requirements.txt @@ -0,0 +1,2 @@ +ldap3==2.9.1 +requests==2.25.1 diff --git a/active-directory/1.1.0/src/app.py b/active-directory/1.1.0/src/app.py new file mode 100644 index 00000000..ad8dec54 --- /dev/null +++ b/active-directory/1.1.0/src/app.py @@ -0,0 +1,422 @@ +import json +import ldap3 +import asyncio +from ldap3 import ( + Server, + Connection, + MODIFY_REPLACE, + ALL_ATTRIBUTES, +) +from walkoff_app_sdk.app_base import AppBase + +class ActiveDirectory(AppBase): + __version__ = "1.1.0" + app_name = "Active Directory" # this needs to match "name" in api.yaml + + def __init__(self, redis, logger, console_logger=None): + """ + Each app should have this __init__ to set up Redis and logging. + :param redis: + :param logger: + :param console_logger: + """ + super().__init__(redis, logger, console_logger) + + def __ldap_connection(self, server, domain, login_user, password, use_ssl): + use_SSL = False if use_ssl.lower() == "false" else False + login_dn = domain + "\\" + login_user + + s = Server(server, use_ssl=use_SSL) + c = Connection(s, user=login_dn, password=password, auto_bind=True) + + return c + + # Decode UserAccountControl code + def __getUserAccountControlAttributes(self, input_code): + userAccountControlFlags = { + 16777216: "TRUSTED_TO_AUTH_FOR_DELEGATION", + 8388608: "PASSWORD_EXPIRED", + 4194304: "DONT_REQ_PREAUTH", + 2097152: "USE_DES_KEY_ONLY", + 1048576: "NOT_DELEGATED", + 524288: "TRUSTED_FOR_DELEGATION", + 262144: "SMARTCARD_REQUIRED", + 131072: "MNS_LOGON_ACCOUNT", + 65536: "DONT_EXPIRE_PASSWORD", + 8192: "SERVER_TRUST_ACCOUNT", + 4096: "WORKSTATION_TRUST_ACCOUNT", + 2048: "INTERDOMAIN_TRUST_ACCOUNT", + 512: "NORMAL_ACCOUNT", + 256: "TEMP_DUPLICATED_ACCOUNT", + 128: "ENCRYPTED_TEXT_PWD_ALLOWED", + 64: "PASSWD_CANT_CHANGE", + 32: "PASSWD_NOTREQD", + 16: "LOCKOUT", + 8: "HOMEDIR_REQUIRED", + 2: "ACCOUNTDISABLED", + 1: "SCRIPT", + } + lists = [] + attributes = {} + while input_code > 0: + for flag, flagName in userAccountControlFlags.items(): + temp = input_code - flag + if temp > 0: + attributes[userAccountControlFlags[flag]] = flag + input_code = temp + if temp == 0: + try: + if userAccountControlFlags[input_code]: + attributes[userAccountControlFlags[input_code]] = input_code + except KeyError: + pass + input_code = temp + for key, val in attributes.items(): + lists.append(key) + return lists + + # Encode UserAccountControl attributes + def __getUserAccountControlCode(self, input_attributes): + userAccountControlFlags = { + "TRUSTED_TO_AUTH_FOR_DELEGATION": 16777216, + "PASSWORD_EXPIRED": 8388608, + "DONT_REQ_PREAUTH": 4194304, + "USE_DES_KEY_ONLY": 2097152, + "NOT_DELEGATED": 1048576, + "TRUSTED_FOR_DELEGATION": 524288, + "SMARTCARD_REQUIRED": 262144, + "MNS_LOGON_ACCOUNT": 131072, + "DONT_EXPIRE_PASSWORD": 65536, + "SERVER_TRUST_ACCOUNT": 8192, + "WORKSTATION_TRUST_ACCOUNT": 4096, + "INTERDOMAIN_TRUST_ACCOUNT": 2048, + "NORMAL_ACCOUNT": 512, + "TEMP_DUPLICATED_ACCOUNT": 256, + "ENCRYPTED_TEXT_PWD_ALLOWED": 128, + "PASSWD_CANT_CHANGE": 64, + "PASSWD_NOTREQD": 32, + "LOCKOUT": 16, + "HOMEDIR_REQUIRED": 8, + "ACCOUNTDISABLED": 2, + "SCRIPT": 1, + } + code = 0 + for attribute in input_attributes: + code += userAccountControlFlags[attribute] + + return code + + # Get User Attributes + def user_attributes( + self, + server, + domain, + login_user, + password, + base_dn, + use_ssl, + samaccountname, + search_base, + ): + if search_base: + base_dn = search_base + + c = self.__ldap_connection(server, domain, login_user, password, use_ssl) + + try: + c.search( + search_base=base_dn, + search_filter=f"(samAccountName={samaccountname})", + attributes=ALL_ATTRIBUTES, + ) + + result = json.loads(c.response_to_json()) + if len(result["entries"]) == 0: + return json.dumps({ + "success": False, + "result": result, + "reason": "No user found for %s" % samaccountname, + }) + + except Exception as e: + return json.dumps({ + "success": False, + "reason": "Failed to get users in user attributes: %s" % e, + }) + + + result = result["entries"][0] + result["attributes"]["userAccountControl"] = self.__getUserAccountControlAttributes(result["attributes"]["userAccountControl"]) + + return json.dumps(result) + + # Change User Password + def set_password( + self, + server, + domain, + login_user, + password, + base_dn, + use_ssl, + samaccountname, + new_password, + repeat_password, + search_base, + ): + if search_base: + base_dn = search_base + + if new_password != repeat_password: + return "Password does not match!" + else: + c = self.__ldap_connection( + server, domain, login_user, password, use_ssl + ) + + result = json.loads( self.user_attributes( server, domain, login_user, password, base_dn, use_ssl, samaccountname, search_base,)) + + user_dn = result["dn"] + c.extend.microsoft.modify_password(user_dn, new_password) + + return json.dumps(c.result) + + # Change User Password at Next Logon + def change_password_at_next_logon( + self, + server, + domain, + login_user, + password, + base_dn, + use_ssl, + samaccountname, + search_base, + ): + if search_base: + base_dn = search_base + + c = self.__ldap_connection(server, domain, login_user, password, use_ssl) + + result = json.loads( + self.user_attributes( + server, + domain, + login_user, + password, + base_dn, + use_ssl, + samaccountname, + search_base, + ) + ) + userAccountControl = result["attributes"]["userAccountControl"] + + if "DONT_EXPIRE_PASSWORD" in userAccountControl: + return "Error: Flag DONT_EXPIRE_PASSWORD is set." + else: + user_dn = result["dn"] + password_expire = {"pwdLastSet": (MODIFY_REPLACE, [0])} + c.modify(dn=user_dn, changes=password_expire) + c.result["samAccountName"] = samaccountname + + return json.dumps(c.result) + + # Enable User + def enable_user( + self, + server, + domain, + login_user, + password, + base_dn, + use_ssl, + samaccountname, + search_base, + ): + + if search_base: + base_dn = search_base + + c = self.__ldap_connection(server, domain, login_user, password, use_ssl) + + result = json.loads( + self.user_attributes( + server, + domain, + login_user, + password, + base_dn, + use_ssl, + samaccountname, + search_base, + ) + ) + + userAccountControl = result["attributes"]["userAccountControl"] + + if "ACCOUNTDISABLED" in userAccountControl: + userAccountControl.remove("ACCOUNTDISABLED") + userAccountControl_code = self.__getUserAccountControlCode( + userAccountControl + ) + new_userAccountControl = { + "userAccountControl": (MODIFY_REPLACE, userAccountControl_code) + } + user_dn = result["dn"] + c.modify(dn=user_dn, changes=new_userAccountControl) + c.result["samAccountName"] = samaccountname + + return json.dumps(c.result) + else: + result = {} + result["samAccountName"] = samaccountname + result["status"] = "success" + result["description"] = "Account already enable" + + return json.dumps(result) + + # Disable User + def disable_user( + self, + server, + domain, + login_user, + password, + base_dn, + use_ssl, + samaccountname, + search_base, + ): + + if search_base: + base_dn = search_base + + c = self.__ldap_connection(server, domain, login_user, password, use_ssl) + + result = json.loads( + self.user_attributes( + server, + domain, + login_user, + password, + base_dn, + use_ssl, + samaccountname, + search_base, + ) + ) + + try: + userAccountControl = result["attributes"]["userAccountControl"] + except Exception as e: + return { + "success": False, + "reason": "Failed to get result attributes: %s" % e, + } + + + if "ACCOUNTDISABLED" in userAccountControl: + try: + result = {} + result["samAccountName"] = samaccountname + result["status"] = "success" + result["description"] = "Account already disable" + result["success"] = True + + return json.dumps(result) + except Exception as e: + return { + "success": False, + "reason": "Failed to send baseresult in disable user: %s" % e, + } + else: + try: + userAccountControl.append("ACCOUNTDISABLED") + userAccountControl_code = self.__getUserAccountControlCode( + userAccountControl + ) + new_userAccountControl = { + "userAccountControl": (MODIFY_REPLACE, userAccountControl_code) + } + user_dn = result["dn"] + c.modify(dn=user_dn, changes=new_userAccountControl) + c.result["samAccountName"] = samaccountname + + return json.dumps(c.result) + except Exception as e: + return { + "success": False, + "reason": "Failed adding ACCOUNTDISABLED to user: %s" % e, + } + + def lock_user(self,server,domain,login_user,password,base_dn,use_ssl,samaccountname,search_base): + + if search_base: + base_dn = search_base + + c = self.__ldap_connection(server, domain, login_user, password, use_ssl) + + c.search(base_dn, f"(SAMAccountName={samaccountname})") + + if len(c.entries) == 0: + return {"success":"false","message":f"User {samaccountname} not found"} + + user_dn = c.entries[0].entry_dn + + c.modify(user_dn, {'userAccountControl':[(MODIFY_REPLACE,[514])]}) + + result = c.result + result["success"] = True + + return result + + def unlock_user(self,server,domain,login_user,password,base_dn,use_ssl,samaccountname,search_base): + + if search_base: + base_dn = search_base + + c = self.__ldap_connection(server, domain, login_user, password, use_ssl) + + c.search(base_dn, f"(SAMAccountName={samaccountname})") + + if len(c.entries) == 0: + return {"success":"false","message":f"User {samaccountname} not found"} + + user_dn = c.entries[0].entry_dn + + c.modify(user_dn, {'userAccountControl':[(MODIFY_REPLACE,[0])]}) + + result = c.result + result["success"] = True + + return result + + def change_user_password_at_next_login(self,server,domain,login_user,password,base_dn,use_ssl,samaccountname,search_base,new_user_password,repeat_new_user_password): + + if search_base: + base_dn = search_base + + if str(new_user_password) != str(repeat_new_user_password): + return {"success":"false","message":"new_user_password and repeat_new_user_password does not match."} + + c = self.__ldap_connection(server, domain, login_user, password, use_ssl) + + c.search(base_dn, f"(SAMAccountName={samaccountname})") + + if len(c.entries) == 0: + return {"success":"false","message":f"User {samaccountname} not found"} + + user_dn = c.entries[0].entry_dn + + c.modify(user_dn, {'pwdLastSet':(MODIFY_REPLACE, [0])}) + c.extend.microsoft.modify_password(user_dn, new_user_password.encode('utf-16-le')) + + result = c.result + result["success"] = True + + return result + + +if __name__ == "__main__": + ActiveDirectory.run() diff --git a/active-directory/1.1.0/src/sample.py b/active-directory/1.1.0/src/sample.py new file mode 100644 index 00000000..0f4c778c --- /dev/null +++ b/active-directory/1.1.0/src/sample.py @@ -0,0 +1,197 @@ +#!/usr/bin/env python3 +import json +import ldap3 +import asyncio +from ldap3 import ( + Server, + Connection, + MODIFY_REPLACE, + ALL_ATTRIBUTES, +) +def __ldap_connection( server, port, domain, login_user, password, use_ssl): + use_SSL = False if use_ssl.lower() == "false" else False + login_dn = domain + "\\" + login_user + + s = Server(server, port=int(port), use_ssl=use_SSL) + c = Connection(s, user=login_dn, password=password, auto_bind=True) + + return c + +def __getUserAccountControlCode(input_attributes): + userAccountControlFlags = { + "TRUSTED_TO_AUTH_FOR_DELEGATION": 16777216, + "PASSWORD_EXPIRED": 8388608, + "DONT_REQ_PREAUTH": 4194304, + "USE_DES_KEY_ONLY": 2097152, + "NOT_DELEGATED": 1048576, + "TRUSTED_FOR_DELEGATION": 524288, + "SMARTCARD_REQUIRED": 262144, + "MNS_LOGON_ACCOUNT": 131072, + "DONT_EXPIRE_PASSWORD": 65536, + "SERVER_TRUST_ACCOUNT": 8192, + "WORKSTATION_TRUST_ACCOUNT": 4096, + "INTERDOMAIN_TRUST_ACCOUNT": 2048, + "NORMAL_ACCOUNT": 512, + "TEMP_DUPLICATED_ACCOUNT": 256, + "ENCRYPTED_TEXT_PWD_ALLOWED": 128, + "PASSWD_CANT_CHANGE": 64, + "PASSWD_NOTREQD": 32, + "LOCKOUT": 16, + "HOMEDIR_REQUIRED": 8, + "ACCOUNTDISABLED": 2, + "SCRIPT": 1, + } + code = 0 + for attribute in input_attributes: + code += userAccountControlFlags[attribute] + + return code + + +# Decode UserAccountControl code +def __getUserAccountControlAttributes(input_code): + userAccountControlFlags = { + 16777216: "TRUSTED_TO_AUTH_FOR_DELEGATION", + 8388608: "PASSWORD_EXPIRED", + 4194304: "DONT_REQ_PREAUTH", + 2097152: "USE_DES_KEY_ONLY", + 1048576: "NOT_DELEGATED", + 524288: "TRUSTED_FOR_DELEGATION", + 262144: "SMARTCARD_REQUIRED", + 131072: "MNS_LOGON_ACCOUNT", + 65536: "DONT_EXPIRE_PASSWORD", + 8192: "SERVER_TRUST_ACCOUNT", + 4096: "WORKSTATION_TRUST_ACCOUNT", + 2048: "INTERDOMAIN_TRUST_ACCOUNT", + 512: "NORMAL_ACCOUNT", + 256: "TEMP_DUPLICATED_ACCOUNT", + 128: "ENCRYPTED_TEXT_PWD_ALLOWED", + 64: "PASSWD_CANT_CHANGE", + 32: "PASSWD_NOTREQD", + 16: "LOCKOUT", + 8: "HOMEDIR_REQUIRED", + 2: "ACCOUNTDISABLED", + 1: "SCRIPT", + } + lists = [] + attributes = {} + while input_code > 0: + for flag, flagName in userAccountControlFlags.items(): + temp = input_code - flag + if temp > 0: + attributes[userAccountControlFlags[flag]] = flag + input_code = temp + if temp == 0: + try: + if userAccountControlFlags[input_code]: + attributes[userAccountControlFlags[input_code]] = input_code + except KeyError: + pass + input_code = temp + for key, val in attributes.items(): + lists.append(key) + return lists + + + +# Disable User +def disable_user( + +server, +port, + domain, + login_user, + password, + base_dn, + use_ssl, + samaccountname, + search_base, +): + + if search_base: + base_dn = search_base + + c = __ldap_connection(server, port, domain, login_user, password, use_ssl) + + result = json.loads( + user_attributes( + server, + port, + domain, + login_user, + password, + base_dn, + use_ssl, + samaccountname, + search_base, + ) + ) + userAccountControl = result["attributes"]["userAccountControl"] + + if "ACCOUNTDISABLED" in userAccountControl: + result = {} + result["samAccountName"] = samaccountname + result["status"] = "success" + result["description"] = "Account already disable" + + return json.dumps(result) + else: + userAccountControl.append("ACCOUNTDISABLED") + userAccountControl_code = __getUserAccountControlCode( + userAccountControl + ) + new_userAccountControl = { + "userAccountControl": (MODIFY_REPLACE, userAccountControl_code) + } + user_dn = result["dn"] + c.modify(dn=user_dn, changes=new_userAccountControl) + c.result["samAccountName"] = samaccountname + + return json.dumps(c.result) + +# Get User Attributes +def user_attributes( + + server, + port, + domain, + login_user, + password, + base_dn, + use_ssl, + samaccountname, + search_base, +): + if search_base: + base_dn = search_base + + c =__ldap_connection(server, port, domain, login_user, password, use_ssl) + + print(c, base_dn, samaccountname) + c.search( + search_base=base_dn, + search_filter=f"(samAccountName={samaccountname})", + attributes=ALL_ATTRIBUTES, + ) + + result = json.loads(c.response_to_json()) + print(result) + result = result["entries"][0] + result["attributes"]["userAccountControl"] = __getUserAccountControlAttributes( + result["attributes"]["userAccountControl"] + ) + + return json.dumps(result) + + +disable_user( + '172.17.12.5', + 389, + 'ICPLAHD', + 'administrator', + 'PW', + 'CN=Users,DC=icplahd,DC=local', + 'false', + 'administrator', + 'CN=Users,DC=icplahd,DC=local', +) From fa9f24dbdde1690a400db0e7421e5db35eafb7dd Mon Sep 17 00:00:00 2001 From: dhaval055 Date: Thu, 27 Apr 2023 14:35:16 +0530 Subject: [PATCH 023/259] re-structerd ad app --- active-directory/1.1.0/Dockerfile | 26 -- active-directory/1.1.0/README.md | 42 --- active-directory/1.1.0/api.yaml | 246 -------------- active-directory/1.1.0/requirements.txt | 2 - active-directory/1.1.0/src/app.py | 422 ------------------------ active-directory/1.1.0/src/sample.py | 197 ----------- 6 files changed, 935 deletions(-) delete mode 100644 active-directory/1.1.0/Dockerfile delete mode 100644 active-directory/1.1.0/README.md delete mode 100644 active-directory/1.1.0/api.yaml delete mode 100644 active-directory/1.1.0/requirements.txt delete mode 100644 active-directory/1.1.0/src/app.py delete mode 100644 active-directory/1.1.0/src/sample.py diff --git a/active-directory/1.1.0/Dockerfile b/active-directory/1.1.0/Dockerfile deleted file mode 100644 index 364e1531..00000000 --- a/active-directory/1.1.0/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -# Base our app image off of the WALKOFF App SDK image -FROM frikky/shuffle:app_sdk as base - -# We're going to stage away all of the bloat from the build tools so lets create a builder stage -FROM base as builder - -# Install all alpine build tools needed for our pip installs -RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev - -# Install all of our pip packages in a single directory that we can copy to our base image later -RUN mkdir /install -WORKDIR /install -COPY requirements.txt /requirements.txt -RUN pip install --prefix="/install" -r /requirements.txt - -# Switch back to our base image and copy in all of our built packages and source code -FROM base -COPY --from=builder /install /usr/local -COPY src /app - -# Install any binary dependencies needed in our final image -# RUN apk --no-cache add --update my_binary_dependency - -# Finally, lets run our app! -WORKDIR /app -CMD python app.py --log-level DEBUG diff --git a/active-directory/1.1.0/README.md b/active-directory/1.1.0/README.md deleted file mode 100644 index e99000f2..00000000 --- a/active-directory/1.1.0/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# Active Directory -Active Directory is used all over the world for different reasons. This app helps you explore and control those users. It's based on an LDAP connection. - -## Authentication -* server: The IP or hostname to connect to -* port: The port to connect to. Default: 389 -* domain: Your CORP domain. Used to login properly together with your login_user -* login_user: Your username. DONT add CORP\\ in front -* password: The password of the user logging in. -* base_dn: The base DN found by running `Get-ADDomain` in powershell, then getting the value of the field "UsersContainer". Should NOT contain spaces. example: `OU=Users,DC=icplahd,DC=com` -* use_ssl: Whether to use SSL to connect to the default port. - -* search_base: Usually same as base_dn - -## Base DN -Finding the Base DN can be done by going to a Windows server in the domain. - -1. Open Powershell -2. Run -``` -Get-ADDomain -``` -3. Find the response from "UsersContainer" and use this for Base DN and Search Base - -## Typical issues -- InvalidCredentials: This happens when the credentials are wrong. See #authentication to understand if your format for your username/password is correct. - -## Features -get user attributes -- done -reset password -- done -change password at next logon -- done -enable/disable user -- done - - -## Upcoming Features -add/remove users to group -- dev -get group attributes -- dev -get group members -- dev -get system attributes -- dev -set system attributes -- dev -change computer OU -- dev -Connect to LDAPs using certificates and TLS diff --git a/active-directory/1.1.0/api.yaml b/active-directory/1.1.0/api.yaml deleted file mode 100644 index 1dde1556..00000000 --- a/active-directory/1.1.0/api.yaml +++ /dev/null @@ -1,246 +0,0 @@ -app_version: 1.1.0 -name: Active Directory -description: Active Directory and LDAP/LDAPS. For full usage of the action configure using LDAPS. -contact_info: - name: "@d4rkw0lv3s" - url: https://github.com/D4rkw0lv3s - email: d4rkw0lv3s@outlook.pt -tags: - - activedirectory - - ldap - - ldaps - - Azure AD -categories: - - IAM - - assets -authentication: - required: true - parameters: - - name: server - description: "Server fqdn or ip address." - example: "server-1.mycompany.com or 127.0.0.1" - required: true - schema: - type: string - - name: domain - description: "Domain to BIND to AD/LDAP with. Should JUST be the NetBIOSName from Get-Addomain" - example: "ICPLAHD" - required: true - schema: - type: string - - name: login_user - description: "Username to BIND to AD/LDAP with" - example: "binduser" - required: true - schema: - type: string - - name: password - description: "Password to BIND with." - example: "Password1IsBad!" - required: true - schema: - type: string - - name: base_dn - description: "Search Base DN" - example: "OU=Users,DC=icplahd,DC=com" - required: true - schema: - type: string - - name: use_ssl - description: "Use SSL Connection Security" - required: true - example: "True" - options: - - "true" - - "false" - schema: - type: string -actions: - - name: user_attributes - description: Query AD for details about a specified user - parameters: - - name: samaccountname - description: user to query - required: true - multiline: false - example: 'user01' - schema: - type: string - - name: search_base - description: "If empty it will use the base_dn." - required: false - multiline: false - example: "OU=Users,DC=mycompany,DC=com" - schema: - type: string - returns: - schema: - type: string - - name: set_password - description: Set password for given user - parameters: - - name: samaccountname - description: user to query - required: true - multiline: false - example: 'user01' - schema: - type: string - - name: new_password - description: user new password - required: true - multiline: false - example: 'Password1IsBad!' - schema: - type: string - - name: repeat_password - description: repeat the new password - required: true - multiline: false - schema: - type: string - - name: search_base - description: "If empty it will use the base_dn." - required: false - multiline: false - example: "OU=Users,DC=mycompany,DC=com" - schema: - type: string - returns: - schema: - type: string - - name: change_password_at_next_logon - description: Force user to change password at next logon - parameters: - - name: samaccountname - description: user to query - required: true - multiline: false - example: 'user01' - schema: - type: string - - name: search_base - description: "If empty it will use the base_dn." - required: false - multiline: false - example: "OU=Users,DC=mycompany,DC=com" - schema: - type: string - returns: - schema: - type: string - - name: enable_user - description: Enable User account - parameters: - - name: samaccountname - description: user to query - required: true - multiline: false - example: 'user01' - schema: - type: string - - name: search_base - description: "If empty it will use the base_dn." - required: false - multiline: false - example: "OU=Users,DC=mycompany,DC=com" - schema: - type: string - returns: - schema: - type: string - - name: disable_user - description: Disable User account - parameters: - - name: samaccountname - description: user to query - required: true - multiline: false - example: 'user01' - schema: - type: string - - name: search_base - description: "If empty it will use the base_dn." - required: false - multiline: false - example: "OU=Users,DC=icplahd,DC=com" - schema: - type: string - returns: - schema: - type: string - - name: lock_user - description: Lock User account - parameters: - - name: samaccountname - description: user to lock - required: true - multiline: false - example: 'user01' - schema: - type: string - - name: search_base - description: "If empty it will use the base_dn." - required: false - multiline: false - example: "OU=Users,DC=icplahd,DC=com" - schema: - type: string - returns: - schema: - type: string - - name: unlock_user - description: Unlock User account - parameters: - - name: samaccountname - description: user to unlock - required: true - multiline: false - example: 'user01' - schema: - type: string - - name: search_base - description: "If empty it will use the base_dn." - required: false - multiline: false - example: "OU=Users,DC=icplahd,DC=com" - schema: - type: string - returns: - schema: - type: string - - name: change_user_password_at_next_login - description: Set given password for user at next login - parameters: - - name: samaccountname - description: user to change password for - required: true - multiline: false - example: 'user01' - schema: - type: string - - name: search_base - description: "If empty it will use the base_dn." - required: false - multiline: false - example: "OU=Users,DC=icplahd,DC=com" - schema: - type: string - - name: new_user_password - description: "New password you want to set" - required: true - multiline: false - example: "***" - schema: - type: string - - name: repeat_new_user_password - description: "Repeat new password you want to set" - required: true - multiline: false - example: "***" - schema: - type: string - returns: - schema: - type: string -large_image: data:image/png;base64,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 diff --git a/active-directory/1.1.0/requirements.txt b/active-directory/1.1.0/requirements.txt deleted file mode 100644 index 5238833e..00000000 --- a/active-directory/1.1.0/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -ldap3==2.9.1 -requests==2.25.1 diff --git a/active-directory/1.1.0/src/app.py b/active-directory/1.1.0/src/app.py deleted file mode 100644 index ad8dec54..00000000 --- a/active-directory/1.1.0/src/app.py +++ /dev/null @@ -1,422 +0,0 @@ -import json -import ldap3 -import asyncio -from ldap3 import ( - Server, - Connection, - MODIFY_REPLACE, - ALL_ATTRIBUTES, -) -from walkoff_app_sdk.app_base import AppBase - -class ActiveDirectory(AppBase): - __version__ = "1.1.0" - app_name = "Active Directory" # this needs to match "name" in api.yaml - - def __init__(self, redis, logger, console_logger=None): - """ - Each app should have this __init__ to set up Redis and logging. - :param redis: - :param logger: - :param console_logger: - """ - super().__init__(redis, logger, console_logger) - - def __ldap_connection(self, server, domain, login_user, password, use_ssl): - use_SSL = False if use_ssl.lower() == "false" else False - login_dn = domain + "\\" + login_user - - s = Server(server, use_ssl=use_SSL) - c = Connection(s, user=login_dn, password=password, auto_bind=True) - - return c - - # Decode UserAccountControl code - def __getUserAccountControlAttributes(self, input_code): - userAccountControlFlags = { - 16777216: "TRUSTED_TO_AUTH_FOR_DELEGATION", - 8388608: "PASSWORD_EXPIRED", - 4194304: "DONT_REQ_PREAUTH", - 2097152: "USE_DES_KEY_ONLY", - 1048576: "NOT_DELEGATED", - 524288: "TRUSTED_FOR_DELEGATION", - 262144: "SMARTCARD_REQUIRED", - 131072: "MNS_LOGON_ACCOUNT", - 65536: "DONT_EXPIRE_PASSWORD", - 8192: "SERVER_TRUST_ACCOUNT", - 4096: "WORKSTATION_TRUST_ACCOUNT", - 2048: "INTERDOMAIN_TRUST_ACCOUNT", - 512: "NORMAL_ACCOUNT", - 256: "TEMP_DUPLICATED_ACCOUNT", - 128: "ENCRYPTED_TEXT_PWD_ALLOWED", - 64: "PASSWD_CANT_CHANGE", - 32: "PASSWD_NOTREQD", - 16: "LOCKOUT", - 8: "HOMEDIR_REQUIRED", - 2: "ACCOUNTDISABLED", - 1: "SCRIPT", - } - lists = [] - attributes = {} - while input_code > 0: - for flag, flagName in userAccountControlFlags.items(): - temp = input_code - flag - if temp > 0: - attributes[userAccountControlFlags[flag]] = flag - input_code = temp - if temp == 0: - try: - if userAccountControlFlags[input_code]: - attributes[userAccountControlFlags[input_code]] = input_code - except KeyError: - pass - input_code = temp - for key, val in attributes.items(): - lists.append(key) - return lists - - # Encode UserAccountControl attributes - def __getUserAccountControlCode(self, input_attributes): - userAccountControlFlags = { - "TRUSTED_TO_AUTH_FOR_DELEGATION": 16777216, - "PASSWORD_EXPIRED": 8388608, - "DONT_REQ_PREAUTH": 4194304, - "USE_DES_KEY_ONLY": 2097152, - "NOT_DELEGATED": 1048576, - "TRUSTED_FOR_DELEGATION": 524288, - "SMARTCARD_REQUIRED": 262144, - "MNS_LOGON_ACCOUNT": 131072, - "DONT_EXPIRE_PASSWORD": 65536, - "SERVER_TRUST_ACCOUNT": 8192, - "WORKSTATION_TRUST_ACCOUNT": 4096, - "INTERDOMAIN_TRUST_ACCOUNT": 2048, - "NORMAL_ACCOUNT": 512, - "TEMP_DUPLICATED_ACCOUNT": 256, - "ENCRYPTED_TEXT_PWD_ALLOWED": 128, - "PASSWD_CANT_CHANGE": 64, - "PASSWD_NOTREQD": 32, - "LOCKOUT": 16, - "HOMEDIR_REQUIRED": 8, - "ACCOUNTDISABLED": 2, - "SCRIPT": 1, - } - code = 0 - for attribute in input_attributes: - code += userAccountControlFlags[attribute] - - return code - - # Get User Attributes - def user_attributes( - self, - server, - domain, - login_user, - password, - base_dn, - use_ssl, - samaccountname, - search_base, - ): - if search_base: - base_dn = search_base - - c = self.__ldap_connection(server, domain, login_user, password, use_ssl) - - try: - c.search( - search_base=base_dn, - search_filter=f"(samAccountName={samaccountname})", - attributes=ALL_ATTRIBUTES, - ) - - result = json.loads(c.response_to_json()) - if len(result["entries"]) == 0: - return json.dumps({ - "success": False, - "result": result, - "reason": "No user found for %s" % samaccountname, - }) - - except Exception as e: - return json.dumps({ - "success": False, - "reason": "Failed to get users in user attributes: %s" % e, - }) - - - result = result["entries"][0] - result["attributes"]["userAccountControl"] = self.__getUserAccountControlAttributes(result["attributes"]["userAccountControl"]) - - return json.dumps(result) - - # Change User Password - def set_password( - self, - server, - domain, - login_user, - password, - base_dn, - use_ssl, - samaccountname, - new_password, - repeat_password, - search_base, - ): - if search_base: - base_dn = search_base - - if new_password != repeat_password: - return "Password does not match!" - else: - c = self.__ldap_connection( - server, domain, login_user, password, use_ssl - ) - - result = json.loads( self.user_attributes( server, domain, login_user, password, base_dn, use_ssl, samaccountname, search_base,)) - - user_dn = result["dn"] - c.extend.microsoft.modify_password(user_dn, new_password) - - return json.dumps(c.result) - - # Change User Password at Next Logon - def change_password_at_next_logon( - self, - server, - domain, - login_user, - password, - base_dn, - use_ssl, - samaccountname, - search_base, - ): - if search_base: - base_dn = search_base - - c = self.__ldap_connection(server, domain, login_user, password, use_ssl) - - result = json.loads( - self.user_attributes( - server, - domain, - login_user, - password, - base_dn, - use_ssl, - samaccountname, - search_base, - ) - ) - userAccountControl = result["attributes"]["userAccountControl"] - - if "DONT_EXPIRE_PASSWORD" in userAccountControl: - return "Error: Flag DONT_EXPIRE_PASSWORD is set." - else: - user_dn = result["dn"] - password_expire = {"pwdLastSet": (MODIFY_REPLACE, [0])} - c.modify(dn=user_dn, changes=password_expire) - c.result["samAccountName"] = samaccountname - - return json.dumps(c.result) - - # Enable User - def enable_user( - self, - server, - domain, - login_user, - password, - base_dn, - use_ssl, - samaccountname, - search_base, - ): - - if search_base: - base_dn = search_base - - c = self.__ldap_connection(server, domain, login_user, password, use_ssl) - - result = json.loads( - self.user_attributes( - server, - domain, - login_user, - password, - base_dn, - use_ssl, - samaccountname, - search_base, - ) - ) - - userAccountControl = result["attributes"]["userAccountControl"] - - if "ACCOUNTDISABLED" in userAccountControl: - userAccountControl.remove("ACCOUNTDISABLED") - userAccountControl_code = self.__getUserAccountControlCode( - userAccountControl - ) - new_userAccountControl = { - "userAccountControl": (MODIFY_REPLACE, userAccountControl_code) - } - user_dn = result["dn"] - c.modify(dn=user_dn, changes=new_userAccountControl) - c.result["samAccountName"] = samaccountname - - return json.dumps(c.result) - else: - result = {} - result["samAccountName"] = samaccountname - result["status"] = "success" - result["description"] = "Account already enable" - - return json.dumps(result) - - # Disable User - def disable_user( - self, - server, - domain, - login_user, - password, - base_dn, - use_ssl, - samaccountname, - search_base, - ): - - if search_base: - base_dn = search_base - - c = self.__ldap_connection(server, domain, login_user, password, use_ssl) - - result = json.loads( - self.user_attributes( - server, - domain, - login_user, - password, - base_dn, - use_ssl, - samaccountname, - search_base, - ) - ) - - try: - userAccountControl = result["attributes"]["userAccountControl"] - except Exception as e: - return { - "success": False, - "reason": "Failed to get result attributes: %s" % e, - } - - - if "ACCOUNTDISABLED" in userAccountControl: - try: - result = {} - result["samAccountName"] = samaccountname - result["status"] = "success" - result["description"] = "Account already disable" - result["success"] = True - - return json.dumps(result) - except Exception as e: - return { - "success": False, - "reason": "Failed to send baseresult in disable user: %s" % e, - } - else: - try: - userAccountControl.append("ACCOUNTDISABLED") - userAccountControl_code = self.__getUserAccountControlCode( - userAccountControl - ) - new_userAccountControl = { - "userAccountControl": (MODIFY_REPLACE, userAccountControl_code) - } - user_dn = result["dn"] - c.modify(dn=user_dn, changes=new_userAccountControl) - c.result["samAccountName"] = samaccountname - - return json.dumps(c.result) - except Exception as e: - return { - "success": False, - "reason": "Failed adding ACCOUNTDISABLED to user: %s" % e, - } - - def lock_user(self,server,domain,login_user,password,base_dn,use_ssl,samaccountname,search_base): - - if search_base: - base_dn = search_base - - c = self.__ldap_connection(server, domain, login_user, password, use_ssl) - - c.search(base_dn, f"(SAMAccountName={samaccountname})") - - if len(c.entries) == 0: - return {"success":"false","message":f"User {samaccountname} not found"} - - user_dn = c.entries[0].entry_dn - - c.modify(user_dn, {'userAccountControl':[(MODIFY_REPLACE,[514])]}) - - result = c.result - result["success"] = True - - return result - - def unlock_user(self,server,domain,login_user,password,base_dn,use_ssl,samaccountname,search_base): - - if search_base: - base_dn = search_base - - c = self.__ldap_connection(server, domain, login_user, password, use_ssl) - - c.search(base_dn, f"(SAMAccountName={samaccountname})") - - if len(c.entries) == 0: - return {"success":"false","message":f"User {samaccountname} not found"} - - user_dn = c.entries[0].entry_dn - - c.modify(user_dn, {'userAccountControl':[(MODIFY_REPLACE,[0])]}) - - result = c.result - result["success"] = True - - return result - - def change_user_password_at_next_login(self,server,domain,login_user,password,base_dn,use_ssl,samaccountname,search_base,new_user_password,repeat_new_user_password): - - if search_base: - base_dn = search_base - - if str(new_user_password) != str(repeat_new_user_password): - return {"success":"false","message":"new_user_password and repeat_new_user_password does not match."} - - c = self.__ldap_connection(server, domain, login_user, password, use_ssl) - - c.search(base_dn, f"(SAMAccountName={samaccountname})") - - if len(c.entries) == 0: - return {"success":"false","message":f"User {samaccountname} not found"} - - user_dn = c.entries[0].entry_dn - - c.modify(user_dn, {'pwdLastSet':(MODIFY_REPLACE, [0])}) - c.extend.microsoft.modify_password(user_dn, new_user_password.encode('utf-16-le')) - - result = c.result - result["success"] = True - - return result - - -if __name__ == "__main__": - ActiveDirectory.run() diff --git a/active-directory/1.1.0/src/sample.py b/active-directory/1.1.0/src/sample.py deleted file mode 100644 index 0f4c778c..00000000 --- a/active-directory/1.1.0/src/sample.py +++ /dev/null @@ -1,197 +0,0 @@ -#!/usr/bin/env python3 -import json -import ldap3 -import asyncio -from ldap3 import ( - Server, - Connection, - MODIFY_REPLACE, - ALL_ATTRIBUTES, -) -def __ldap_connection( server, port, domain, login_user, password, use_ssl): - use_SSL = False if use_ssl.lower() == "false" else False - login_dn = domain + "\\" + login_user - - s = Server(server, port=int(port), use_ssl=use_SSL) - c = Connection(s, user=login_dn, password=password, auto_bind=True) - - return c - -def __getUserAccountControlCode(input_attributes): - userAccountControlFlags = { - "TRUSTED_TO_AUTH_FOR_DELEGATION": 16777216, - "PASSWORD_EXPIRED": 8388608, - "DONT_REQ_PREAUTH": 4194304, - "USE_DES_KEY_ONLY": 2097152, - "NOT_DELEGATED": 1048576, - "TRUSTED_FOR_DELEGATION": 524288, - "SMARTCARD_REQUIRED": 262144, - "MNS_LOGON_ACCOUNT": 131072, - "DONT_EXPIRE_PASSWORD": 65536, - "SERVER_TRUST_ACCOUNT": 8192, - "WORKSTATION_TRUST_ACCOUNT": 4096, - "INTERDOMAIN_TRUST_ACCOUNT": 2048, - "NORMAL_ACCOUNT": 512, - "TEMP_DUPLICATED_ACCOUNT": 256, - "ENCRYPTED_TEXT_PWD_ALLOWED": 128, - "PASSWD_CANT_CHANGE": 64, - "PASSWD_NOTREQD": 32, - "LOCKOUT": 16, - "HOMEDIR_REQUIRED": 8, - "ACCOUNTDISABLED": 2, - "SCRIPT": 1, - } - code = 0 - for attribute in input_attributes: - code += userAccountControlFlags[attribute] - - return code - - -# Decode UserAccountControl code -def __getUserAccountControlAttributes(input_code): - userAccountControlFlags = { - 16777216: "TRUSTED_TO_AUTH_FOR_DELEGATION", - 8388608: "PASSWORD_EXPIRED", - 4194304: "DONT_REQ_PREAUTH", - 2097152: "USE_DES_KEY_ONLY", - 1048576: "NOT_DELEGATED", - 524288: "TRUSTED_FOR_DELEGATION", - 262144: "SMARTCARD_REQUIRED", - 131072: "MNS_LOGON_ACCOUNT", - 65536: "DONT_EXPIRE_PASSWORD", - 8192: "SERVER_TRUST_ACCOUNT", - 4096: "WORKSTATION_TRUST_ACCOUNT", - 2048: "INTERDOMAIN_TRUST_ACCOUNT", - 512: "NORMAL_ACCOUNT", - 256: "TEMP_DUPLICATED_ACCOUNT", - 128: "ENCRYPTED_TEXT_PWD_ALLOWED", - 64: "PASSWD_CANT_CHANGE", - 32: "PASSWD_NOTREQD", - 16: "LOCKOUT", - 8: "HOMEDIR_REQUIRED", - 2: "ACCOUNTDISABLED", - 1: "SCRIPT", - } - lists = [] - attributes = {} - while input_code > 0: - for flag, flagName in userAccountControlFlags.items(): - temp = input_code - flag - if temp > 0: - attributes[userAccountControlFlags[flag]] = flag - input_code = temp - if temp == 0: - try: - if userAccountControlFlags[input_code]: - attributes[userAccountControlFlags[input_code]] = input_code - except KeyError: - pass - input_code = temp - for key, val in attributes.items(): - lists.append(key) - return lists - - - -# Disable User -def disable_user( - -server, -port, - domain, - login_user, - password, - base_dn, - use_ssl, - samaccountname, - search_base, -): - - if search_base: - base_dn = search_base - - c = __ldap_connection(server, port, domain, login_user, password, use_ssl) - - result = json.loads( - user_attributes( - server, - port, - domain, - login_user, - password, - base_dn, - use_ssl, - samaccountname, - search_base, - ) - ) - userAccountControl = result["attributes"]["userAccountControl"] - - if "ACCOUNTDISABLED" in userAccountControl: - result = {} - result["samAccountName"] = samaccountname - result["status"] = "success" - result["description"] = "Account already disable" - - return json.dumps(result) - else: - userAccountControl.append("ACCOUNTDISABLED") - userAccountControl_code = __getUserAccountControlCode( - userAccountControl - ) - new_userAccountControl = { - "userAccountControl": (MODIFY_REPLACE, userAccountControl_code) - } - user_dn = result["dn"] - c.modify(dn=user_dn, changes=new_userAccountControl) - c.result["samAccountName"] = samaccountname - - return json.dumps(c.result) - -# Get User Attributes -def user_attributes( - - server, - port, - domain, - login_user, - password, - base_dn, - use_ssl, - samaccountname, - search_base, -): - if search_base: - base_dn = search_base - - c =__ldap_connection(server, port, domain, login_user, password, use_ssl) - - print(c, base_dn, samaccountname) - c.search( - search_base=base_dn, - search_filter=f"(samAccountName={samaccountname})", - attributes=ALL_ATTRIBUTES, - ) - - result = json.loads(c.response_to_json()) - print(result) - result = result["entries"][0] - result["attributes"]["userAccountControl"] = __getUserAccountControlAttributes( - result["attributes"]["userAccountControl"] - ) - - return json.dumps(result) - - -disable_user( - '172.17.12.5', - 389, - 'ICPLAHD', - 'administrator', - 'PW', - 'CN=Users,DC=icplahd,DC=local', - 'false', - 'administrator', - 'CN=Users,DC=icplahd,DC=local', -) From 6dd550eeae7d1d73adf34191822cf6ad35082005 Mon Sep 17 00:00:00 2001 From: dhaval055 Date: Fri, 28 Apr 2023 11:55:01 +0530 Subject: [PATCH 024/259] added actions to lock/unlock user, change pass --- active-directory/1.0.0/api.yaml | 74 +++++++++++++++++++++++++++++++ active-directory/1.0.0/src/app.py | 67 ++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) diff --git a/active-directory/1.0.0/api.yaml b/active-directory/1.0.0/api.yaml index 526de3fd..f9c6a3d6 100644 --- a/active-directory/1.0.0/api.yaml +++ b/active-directory/1.0.0/api.yaml @@ -175,4 +175,78 @@ actions: returns: schema: type: string + - name: lock_user + description: Lock User account + parameters: + - name: samaccountname + description: user to lock + required: true + multiline: false + example: 'user01' + schema: + type: string + - name: search_base + description: "If empty it will use the base_dn." + required: false + multiline: false + example: "OU=Users,DC=icplahd,DC=com" + schema: + type: string + returns: + schema: + type: string + - name: unlock_user + description: Unlock User account + parameters: + - name: samaccountname + description: user to unlock + required: true + multiline: false + example: 'user01' + schema: + type: string + - name: search_base + description: "If empty it will use the base_dn." + required: false + multiline: false + example: "OU=Users,DC=icplahd,DC=com" + schema: + type: string + returns: + schema: + type: string + - name: change_user_password_at_next_login + description: Set given password for user at next login + parameters: + - name: samaccountname + description: user to change password for + required: true + multiline: false + example: 'user01' + schema: + type: string + - name: search_base + description: "If empty it will use the base_dn." + required: false + multiline: false + example: "OU=Users,DC=icplahd,DC=com" + schema: + type: string + - name: new_user_password + description: "New password you want to set" + required: true + multiline: false + example: "***" + schema: + type: string + - name: repeat_new_user_password + description: "Repeat new password you want to set" + required: true + multiline: false + example: "***" + schema: + type: string + returns: + schema: + type: string large_image: data:image/png;base64,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 diff --git a/active-directory/1.0.0/src/app.py b/active-directory/1.0.0/src/app.py index 3930dcf9..5d1dd114 100644 --- a/active-directory/1.0.0/src/app.py +++ b/active-directory/1.0.0/src/app.py @@ -358,6 +358,73 @@ def disable_user( "reason": "Failed adding ACCOUNTDISABLED to user: %s" % e, } + def lock_user(self,server,domain,port,login_user,password,base_dn,use_ssl,samaccountname,search_base): + + if search_base: + base_dn = search_base + + c = self.__ldap_connection(server, port, domain, login_user, password, use_ssl) + + c.search(base_dn, f"(SAMAccountName={samaccountname})") + + if len(c.entries) == 0: + return {"success":"false","message":f"User {samaccountname} not found"} + + user_dn = c.entries[0].entry_dn + + c.modify(user_dn, {'userAccountControl':[(MODIFY_REPLACE,[514])]}) + + result = c.result + result["success"] = True + + return result + + def unlock_user(self,server,domain,port,login_user,password,base_dn,use_ssl,samaccountname,search_base): + + if search_base: + base_dn = search_base + + c = self.__ldap_connection(server, port, domain, login_user, password, use_ssl) + + c.search(base_dn, f"(SAMAccountName={samaccountname})") + + if len(c.entries) == 0: + return {"success":"false","message":f"User {samaccountname} not found"} + + user_dn = c.entries[0].entry_dn + + c.modify(user_dn, {'userAccountControl':[(MODIFY_REPLACE,[0])]}) + + result = c.result + result["success"] = True + + return result + + def change_user_password_at_next_login(self,server,domain,port,login_user,password,base_dn,use_ssl,samaccountname,search_base,new_user_password,repeat_new_user_password): + + if search_base: + base_dn = search_base + + if str(new_user_password) != str(repeat_new_user_password): + return {"success":"false","message":"new_user_password and repeat_new_user_password does not match."} + + c = self.__ldap_connection(server, port, domain, login_user, password, use_ssl) + + c.search(base_dn, f"(SAMAccountName={samaccountname})") + + if len(c.entries) == 0: + return {"success":"false","message":f"User {samaccountname} not found"} + + user_dn = c.entries[0].entry_dn + + c.modify(user_dn, {'pwdLastSet':(MODIFY_REPLACE, [0])}) + c.extend.microsoft.modify_password(user_dn, new_user_password.encode('utf-16-le')) + + result = c.result + result["success"] = True + + return result + if __name__ == "__main__": ActiveDirectory.run() From 6e207c9cb82738827596fff58ad9be5429638aa3 Mon Sep 17 00:00:00 2001 From: dhaval055 Date: Thu, 4 May 2023 19:02:24 +0530 Subject: [PATCH 025/259] added password auth to SSH action --- shuffle-tools/1.2.0/api.yaml | 16 ++++++++++- shuffle-tools/1.2.0/requirements.txt | 1 + shuffle-tools/1.2.0/src/app.py | 42 ++++++++++++++++++---------- 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/shuffle-tools/1.2.0/api.yaml b/shuffle-tools/1.2.0/api.yaml index b76c5a74..50f19a25 100644 --- a/shuffle-tools/1.2.0/api.yaml +++ b/shuffle-tools/1.2.0/api.yaml @@ -1076,6 +1076,13 @@ actions: example: '192.168.55.11' schema: type: string + - name: port + description: A port on which SSH service is running + required: false + multiline: false + example: 'Default is 22' + schema: + type: string - name: user_name description: User on remote system required: true @@ -1085,11 +1092,18 @@ actions: type: string - name: private_key_file_id description: Private key file ID - required: true + required: false multiline: false example: 'file_c5c87a50-4146-40e2-a698-78cf13bf65c0' schema: type: string + - name: password + description: Password for SSH user. Use either password or private key. + required: false + multiline: false + example: '***' + schema: + type: string - name: command description: Command you want to run required: true diff --git a/shuffle-tools/1.2.0/requirements.txt b/shuffle-tools/1.2.0/requirements.txt index b9000087..1dc466c6 100644 --- a/shuffle-tools/1.2.0/requirements.txt +++ b/shuffle-tools/1.2.0/requirements.txt @@ -7,3 +7,4 @@ xmltodict==0.11.0 json2xml==3.6.0 ipaddress==1.0.23 google.auth==1.23.0 +paramiko==3.1.0 diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index ebb4cf7a..d9bc645b 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -35,6 +35,8 @@ import binascii import struct +import paramiko + from walkoff_app_sdk.app_base import AppBase class Tools(AppBase): @@ -2332,26 +2334,38 @@ def generate_random_string(length=16, special_characters=True): "password": password, } - def run_ssh_command(self, host, user_name, private_key_file_id, command): + def run_ssh_command(self, host, port,user_name, private_key_file_id, password, command): new_file = self.get_file(private_key_file_id) ssh_client = paramiko.SSHClient() ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - try: - key_data = new_file["data"].decode() - except Exception as e: - return {"success":"false","message":str(e)} + if port: + port = int(port) + else: + port = 22 - private_key_file = io.StringIO() - private_key_file.write(key_data) - private_key_file.seek(0) - private_key = paramiko.RSAKey.from_private_key(private_key_file) - - try: - ssh_client.connect(hostname=host, username=user_name, pkey= private_key) - except Exception as e: - return {"success":"false","message":str(e)} + if private_key_file_id: + try: + key_data = new_file["data"].decode() + except Exception as e: + return {"success":"false","message":str(e)} + + private_key_file = StringIO() + private_key_file.write(key_data) + private_key_file.seek(0) + private_key = paramiko.RSAKey.from_private_key(private_key_file) + + try: + ssh_client.connect(hostname=host,username=user_name,port=port, pkey= private_key) + except Exception as e: + return {"success":"false","message":str(e)} + else: + print("AUTH WITH PASSWORD") + try: + ssh_client.connect(hostname=host,username=user_name,port=port, password=str(password)) + except Exception as e: + return {"success":"false","message":str(e)} try: stdin, stdout, stderr = ssh_client.exec_command(str(command)) From aba131ba81e32a35e8c33b522b47b2abc5b2da8b Mon Sep 17 00:00:00 2001 From: frikky Date: Thu, 11 May 2023 11:40:30 +0200 Subject: [PATCH 026/259] Minor fixes for b64 and ssh command --- email/1.2.0/src/app.py | 2 +- http/1.3.0/api.yaml | 2 +- http/1.3.0/src/app.py | 1 - microsoft-excel/1.0.0/api.yaml | 25 +++++++++++++------------ microsoft-excel/1.0.0/src/app.py | 20 +++++++++++++++----- shuffle-tools/1.2.0/src/app.py | 18 +++++++++++++++--- 6 files changed, 45 insertions(+), 23 deletions(-) diff --git a/email/1.2.0/src/app.py b/email/1.2.0/src/app.py index 4a17f0f6..3187c900 100644 --- a/email/1.2.0/src/app.py +++ b/email/1.2.0/src/app.py @@ -496,7 +496,7 @@ def analyze_headers(self, headers): if "dmarc" in item["key"]: print("dmarc: ", item["key"]) - if item["key"] == "authentication-results": + if item["key"].lower() == "authentication-results": if "spf=pass" in item["value"]: spf = True if "dkim=pass" in item["value"]: diff --git a/http/1.3.0/api.yaml b/http/1.3.0/api.yaml index e30294a7..1fee36c4 100644 --- a/http/1.3.0/api.yaml +++ b/http/1.3.0/api.yaml @@ -163,7 +163,7 @@ actions: type: string example: "404 NOT FOUND" - name: PATCH - description: Runs a PATCHrequest towards the specified endpoint + description: Runs a PATCH request towards the specified endpoint parameters: - name: url description: The URL to post to diff --git a/http/1.3.0/src/app.py b/http/1.3.0/src/app.py index ddebbf6c..6ed58694 100755 --- a/http/1.3.0/src/app.py +++ b/http/1.3.0/src/app.py @@ -256,7 +256,6 @@ def POST(self, url, headers="", body="", username="", password="", verify=True, return self.return_file(request.text) - # UNTESTED BELOW HERE def PUT(self, url, headers="", body="", username="", password="", verify=True, http_proxy="", https_proxy="", timeout=5, to_file=False): url = self.fix_url(url) diff --git a/microsoft-excel/1.0.0/api.yaml b/microsoft-excel/1.0.0/api.yaml index d52baafd..1ffbb35a 100644 --- a/microsoft-excel/1.0.0/api.yaml +++ b/microsoft-excel/1.0.0/api.yaml @@ -30,6 +30,19 @@ authentication: schema: type: string actions: + - name: get_excel_file_data + description: Gets data from all cells in an excel file as a list. If CSV, returns it as a CSV list + auth_not_required: true + parameters: + - name: file_id + description: The file id of the file + multiline: false + required: true + schema: + type: string + returns: + schema: + type: string - name: get_user_id description: Returns all users - name: get_files @@ -176,16 +189,4 @@ actions: # returns: # schema: # type: string - - name: get_excel_file_data - description: Gets data from all cells in an excel file as a list - parameters: - - name: file_id - description: The file id of the file - multiline: false - required: true - schema: - type: string - returns: - schema: - type: string large_image: data:image/png;base64,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 diff --git a/microsoft-excel/1.0.0/src/app.py b/microsoft-excel/1.0.0/src/app.py index 7d0b1a13..67277a29 100644 --- a/microsoft-excel/1.0.0/src/app.py +++ b/microsoft-excel/1.0.0/src/app.py @@ -6,10 +6,12 @@ import uuid import time import requests -from openpyxl import Workbook, load_workbook +from walkoff_app_sdk import csv_parse from walkoff_app_sdk.app_base import AppBase +from openpyxl import Workbook, load_workbook + class MSExcel(AppBase): __version__ = "1.0.0" app_name = "Microsoft Excel" @@ -123,7 +125,7 @@ def convert_to_csv(self, tenant_id, client_id, client_secret, file_id, sheet="Sh if filedata["success"] != True: return filedata - basename = "file.xlsx" + basename = "/tmp/file.xlsx" with open(basename, "wb") as tmp: tmp.write(filedata["data"]) @@ -163,9 +165,17 @@ def get_excel_file_data(self, file_id): if filedata["success"] != True: print(f"Bad info from file: {filedata}") return filedata - #filedata = file_id + + try: + print("Filename: %s" % filedata["filename"]) + if "csv" in filedata["filename"]: + filedata["data"] = filedata["data"].decode("utf-8") + returndata = csv_parse(filedata["data"]) + return returndata + except Exception as e: + print("Error parsing file with csv parser for file %s: %s" % (filedata["filename"], e)) - basename = "file.xlsx" + basename = "/tmp/file.xlsx" with open(basename, "wb") as tmp: tmp.write(filedata["data"]) @@ -175,7 +185,7 @@ def get_excel_file_data(self, file_id): except Exception as e: return { "success": False, - "reason": "The file is invalid. Are you sure it's a valid excel file?", + "reason": "The file is invalid. Are you sure it's a valid excel file? CSV files are not supported." "exception": "Error: %s" % e, } diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index d9bc645b..9e656b0f 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -59,6 +59,12 @@ def router(self): def base64_conversion(self, string, operation): if operation == "encode": + # Try JSON decoding + try: + string = json.dumps(json.loads(string)) + except: + pass + encoded_bytes = base64.b64encode(str(string).encode("utf-8")) encoded_string = str(encoded_bytes, "utf-8") return encoded_string @@ -71,6 +77,12 @@ def base64_conversion(self, string, operation): except: pass + # Check if json + try: + decoded_bytes = json.loads(decoded_bytes) + except: + pass + return decoded_bytes except Exception as e: #return string.decode("utf-16") @@ -2334,9 +2346,7 @@ def generate_random_string(length=16, special_characters=True): "password": password, } - def run_ssh_command(self, host, port,user_name, private_key_file_id, password, command): - new_file = self.get_file(private_key_file_id) - + def run_ssh_command(self, host, port, user_name, private_key_file_id, password, command): ssh_client = paramiko.SSHClient() ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) @@ -2346,6 +2356,8 @@ def run_ssh_command(self, host, port,user_name, private_key_file_id, password, c port = 22 if private_key_file_id: + new_file = self.get_file(private_key_file_id) + try: key_data = new_file["data"].decode() except Exception as e: From 372e3909156fc6ad87aaa1413a196b8e645f0e93 Mon Sep 17 00:00:00 2001 From: frikky Date: Mon, 15 May 2023 02:05:00 +0200 Subject: [PATCH 027/259] Autobuild with new sdk --- shuffle-tools/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/shuffle-tools/README.md b/shuffle-tools/README.md index b0271276..aaa07dbb 100644 --- a/shuffle-tools/README.md +++ b/shuffle-tools/README.md @@ -1,7 +1,6 @@ # Shuffle tools App documentation Shuffle tools is a utility app that simplifies your understanding of what happens in a node and also allows you to test on the fly. - ## Actions The Shuffle-tools app comes with a multitude of different actions, here we will check a few out and give a brief description. From cee04427a2e1ebbbc51b517a72000af2834a0266 Mon Sep 17 00:00:00 2001 From: frikky Date: Mon, 15 May 2023 03:42:49 +0200 Subject: [PATCH 028/259] Readme fix to rebuild apps for 3.11 --- shuffle-tools/README.md | 107 ++++++++++++++-------------------------- 1 file changed, 36 insertions(+), 71 deletions(-) diff --git a/shuffle-tools/README.md b/shuffle-tools/README.md index aaa07dbb..febda00c 100644 --- a/shuffle-tools/README.md +++ b/shuffle-tools/README.md @@ -4,74 +4,39 @@ Shuffle tools is a utility app that simplifies your understanding of what happen ## Actions The Shuffle-tools app comes with a multitude of different actions, here we will check a few out and give a brief description. -1. Repeat back to me - This action does exactly what it says, repeats back to you what you want it to. Why is this important? You need to test as you go whilst creating your workflow, what results does the first node give and are the results okay to use in the subsequent nodes? - -2. Router - Reroutes data between different nodes. - -3. Check cache contains - Checks Shuffle cache whether a key contains a value in a list - -4. Get cache value - Get a value savesd in your Shuffle organization - -5. Send SMS Shuffle - Sends an SMS from Shuffle, currently working on getting a few demo trials. - -6. Send E-mail Shuffle - Sends an Email from shuffle, currently working on getting a few demo trials. - -7. Filter list - Takes a list and filters based on the data - -8. Parse IOC - Parses Indicators of Compromise based on https://github.com/fhightower/ioc-finder - -9. Translate Value - Takes a list of values and translates it in your input data - -10. Map value - Takes a mapping dictionary and translates the input data. This is a search and replace for multiple fields. - -11. Regex Capture Group - Returns objects matching the capture group - -12. Regex replace - Replace all instances matching the regular expression - -13. Parse List - Parses a list and returns it as a JSON object - -14. Execute Bash - Runs bash with the data input - -15. Execute Python - Runs python with the data input. Any prints will be returned. - -16. Get file value - This function is made for reading files. Prints out their data. - -17. Download remote file - Downloads a file from a url - -18. Get file meta - Gets file metadata - -19. Delete file - Delete's file based on id - -20. Extract archive - Extracts compressed files and returns file ids - -21. Inflate archive - Compress files in an archive. Return file archive ids - -22. XML JSON converter - Converts XML to JSON and vice versa - -23. Date to epoch - converts a date field with a given format to an epoch time - -24. Compare relative date - Compares an input date and a relative date and returns a True/False response - -25. Add list to list - Can append single items to a list, can also add items of a list to another list - -26. Merge lists - Merge lists of the same type and length - -27. Diff Lists - Differentiates two lists of strings or integers and finds what's missing - -28. Set JSON Key - Adds a JSON key to an existing object - -29. Delete JSON Keys - Deletes keys in a JSON object - -30. Convert JSON tags - Creates Key:Value pairs and converts JSON to tags - -31. Run Math Operation - Runs an arithmetic operation - -32. Escape HTML - Performs HTML escaping on field - -33. Base 64 Conversion - Encodes or Decodes a base64 string - -34. Get time stamp - Gets a timestamp for right now. Default returns epoch time - -35. Get Hash sum - Returns multiple format of hashes based on the input value - -36. Cidr IP match - Check if an IP is contained in a CIDR defined network +- 1. Repeat back to me - This action does exactly what it says, repeats back to you what you want it to. Why is this important? You need to test as you go whilst creating your workflow, what results does the first node give and are the results okay to use in the subsequent nodes? +- 2. Router - Reroutes data between different nodes. +- 3. Check cache contains - Checks Shuffle cache whether a key contains a value in a list +- 4. Get cache value - Get a value savesd in your Shuffle organization +- 5. Send SMS Shuffle - Sends an SMS from Shuffle, currently working on getting a few demo trials. +- 6. Send E-mail Shuffle - Sends an Email from shuffle, currently working on getting a few demo trials. +- 7. Filter list - Takes a list and filters based on the data +- 8. Parse IOC - Parses Indicators of Compromise based on https://github.com/fhightower/ioc-finder +- 9. Translate Value - Takes a list of values and translates it in your input data +- 10. Map value - Takes a mapping dictionary and translates the input data. This is a search and replace for multiple fields. +- 11. Regex Capture Group - Returns objects matching the capture group +- 12. Regex replace - Replace all instances matching the regular expression +- 13. Parse List - Parses a list and returns it as a JSON object +- 14. Execute Bash - Runs bash with the data input +- 15. Execute Python - Runs python with the data input. Any prints will be returned. +- 16. Get file value - This function is made for reading files. Prints out their data. +- 17. Download remote file - Downloads a file from a url +- 18. Get file meta - Gets file metadata +- 19. Delete file - Delete's file based on id +- 20. Extract archive - Extracts compressed files and returns file ids +- 21. Inflate archive - Compress files in an archive. Return file archive ids +- 22. XML JSON converter - Converts XML to JSON and vice versa +- 23. Date to epoch - converts a date field with a given format to an epoch time +- 24. Compare relative date - Compares an input date and a relative date and returns a True/False response +- 25. Add list to list - Can append single items to a list, can also add items of a list to another list +- 26. Merge lists - Merge lists of the same type and length +- 27. Diff Lists - Differentiates two lists of strings or integers and finds what's missing +- 28. Set JSON Key - Adds a JSON key to an existing object +- 29. Delete JSON Keys - Deletes keys in a JSON object +- 30. Convert JSON tags - Creates Key:Value pairs and converts JSON to tags +- 31. Run Math Operation - Runs an arithmetic operation +- 32. Escape HTML - Performs HTML escaping on field +- 33. Base 64 Conversion - Encodes or Decodes a base64 string +- 34. Get time stamp - Gets a timestamp for right now. Default returns epoch time +- 35. Get Hash sum - Returns multiple format of hashes based on the input value +- 36. Cidr IP match - Check if an IP is contained in a CIDR defined network From e33a5dd2324be044d26425356d7c26cb1002e010 Mon Sep 17 00:00:00 2001 From: frikky Date: Mon, 15 May 2023 04:40:12 +0200 Subject: [PATCH 029/259] Rebuild with old datatypes. Upgrading to 3.11 has fucked up a lot (: --- shuffle-tools/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/shuffle-tools/README.md b/shuffle-tools/README.md index febda00c..0a3518ac 100644 --- a/shuffle-tools/README.md +++ b/shuffle-tools/README.md @@ -1,6 +1,7 @@ # Shuffle tools App documentation Shuffle tools is a utility app that simplifies your understanding of what happens in a node and also allows you to test on the fly. + ## Actions The Shuffle-tools app comes with a multitude of different actions, here we will check a few out and give a brief description. From 9caf098069c9a19544dc90073fcc21c830bff422 Mon Sep 17 00:00:00 2001 From: frikky Date: Tue, 16 May 2023 23:58:03 +0200 Subject: [PATCH 030/259] Subflow & excel fixes for auth --- microsoft-excel/1.0.0/src/app.py | 12 ++++++++-- shuffle-subflow/1.0.0/src/app.py | 38 ++++++++++++++++++++++++++------ shuffle-subflow/1.1.0/src/app.py | 10 +++++---- shuffle-tools/1.2.0/src/app.py | 14 ++++++++---- 4 files changed, 57 insertions(+), 17 deletions(-) diff --git a/microsoft-excel/1.0.0/src/app.py b/microsoft-excel/1.0.0/src/app.py index 67277a29..8baa18f0 100644 --- a/microsoft-excel/1.0.0/src/app.py +++ b/microsoft-excel/1.0.0/src/app.py @@ -169,9 +169,17 @@ def get_excel_file_data(self, file_id): try: print("Filename: %s" % filedata["filename"]) if "csv" in filedata["filename"]: - filedata["data"] = filedata["data"].decode("utf-8") + try: + filedata["data"] = filedata["data"].decode("utf-8") + except: + try: + filedata["data"] = filedata["data"].decode("utf-16") + except: + filedata["data"] = filedata["data"].decode("latin-1") + returndata = csv_parse(filedata["data"]) return returndata + except Exception as e: print("Error parsing file with csv parser for file %s: %s" % (filedata["filename"], e)) @@ -185,7 +193,7 @@ def get_excel_file_data(self, file_id): except Exception as e: return { "success": False, - "reason": "The file is invalid. Are you sure it's a valid excel file? CSV files are not supported." + "reason": "The file is invalid. Are you sure it's a valid excel file? CSV files are not supported.", "exception": "Error: %s" % e, } diff --git a/shuffle-subflow/1.0.0/src/app.py b/shuffle-subflow/1.0.0/src/app.py index e57fd7ef..2fc46527 100644 --- a/shuffle-subflow/1.0.0/src/app.py +++ b/shuffle-subflow/1.0.0/src/app.py @@ -21,23 +21,34 @@ def __init__(self, redis, logger, console_logger=None): super().__init__(redis, logger, console_logger) # Should run user input - def run_userinput(self, user_apikey, sms="", email="", subflow="", information="", startnode="", backend_url=""): + def run_userinput(self, user_apikey, sms="", email="", subflow="", information="", startnode="", backend_url="", source_node=""): #url = "%s/api/v1/workflows/%s/execute" % (self.url, workflow) headers = { "Authorization": "Bearer %s" % user_apikey, - "User-Agent": "Shuffle Userinput 1.0.0" + "User-Agent": "Shuffle Userinput 1.1.0" } result = { "success": True, "source": "userinput", - "reason": "Userinput data sent and workflow paused. Waiting for user input before continuing workflow." + "reason": "Userinput data sent and workflow paused. Waiting for user input before continuing workflow.", + "information": information, + "click_info": { + "clicked": False, + "time": "", + "ip": "", + "user": "", + "note": "", + } } url = self.url + if len(self.base_url) > 0: + url = self.base_url + if len(str(backend_url)) > 0: - url = "%s" % (backend_url) + url = backend_url print("Found backend url: %s" % url) #if len(information): @@ -50,20 +61,31 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" #print("Subflows to run from userinput: ", subflows) subflows = subflow.split(",") + frontend_url = url + if ":5001" in frontend_url: + print("Should change port to 3001.") + if "appspot.com" in frontend_url: + frontend_url = "https://shuffler.io" + for item in subflows: # In case of URL being passed, and not just ID if "/" in item: item = item.split("/")[-1] + # Subflow should be the subflow to run + # Workflow in the URL should be the source workflow argument = json.dumps({ "information": information, "parent_workflow": self.full_execution["workflow"]["id"], - "continue_url": "%s/api/v1/workflows/%s/execute?authorization=%s&reference_execution=%s&answer=true" % (url, item, user_apikey, self.full_execution["execution_id"]), - "abort_url": "%s/api/v1/workflows/%s/execute?authorization=%s&reference_execution=%s&answer=true" % (url, item, user_apikey, self.full_execution["execution_id"]), + "frontend_continue": "%s/workflows/%s/run?authorization=%s&reference_execution=%s&answer=true" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"]), + "frontend_abort": "%s/workflows/%s/run?authorization=%s&reference_execution=%s&answer=false" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"]), + "api_continue": "%s/api/v1/workflows/%s/execute?authorization=%s&reference_execution=%s&answer=true" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"]), + "api_abort": "%s/api/v1/workflows/%s/execute?authorization=%s&reference_execution=%s&answer=false" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"]), }) - ret = self.run_subflow(user_apikey, item, argument, source_workflow=self.full_execution["workflow"]["id"], source_execution=self.full_execution["execution_id"], source_auth=self.full_execution["authorization"], startnode=startnode, backend_url=backend_url) + ret = self.run_subflow(user_apikey, item, argument, source_workflow=self.full_execution["workflow"]["id"], source_execution=self.full_execution["execution_id"], source_auth=self.full_execution["authorization"], startnode=startnode, backend_url=backend_url, source_node=source_node) result["subflow"] = ret + result["subflow_url"] = "%s/workflows/%s" % (frontend_url, item) if len(email): jsondata = { @@ -74,6 +96,7 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" "start": startnode, "workflow_id": self.full_execution["workflow"]["id"], "reference_execution": self.full_execution["execution_id"], + "authorization": self.full_execution["authorization"], } for item in email.split(","): @@ -98,6 +121,7 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" "start": startnode, "workflow_id": self.full_execution["workflow"]["id"], "reference_execution": self.full_execution["execution_id"], + "authorization": self.full_execution["authorization"], } for item in sms.split(","): diff --git a/shuffle-subflow/1.1.0/src/app.py b/shuffle-subflow/1.1.0/src/app.py index 926912e8..f1fa452a 100644 --- a/shuffle-subflow/1.1.0/src/app.py +++ b/shuffle-subflow/1.1.0/src/app.py @@ -51,6 +51,7 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" url = backend_url print("Found backend url: %s" % url) + print("AUTH: %s" % self.full_execution["authorization"]) #if len(information): # print("Should run arg: %s", information) @@ -67,6 +68,7 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" if "appspot.com" in frontend_url: frontend_url = "https://shuffler.io" + for item in subflows: # In case of URL being passed, and not just ID if "/" in item: @@ -77,10 +79,10 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" argument = json.dumps({ "information": information, "parent_workflow": self.full_execution["workflow"]["id"], - "frontend_continue": "%s/workflows/%s/run?authorization=%s&reference_execution=%s&answer=true" % (frontend_url, self.full_execution["workflow"]["id"], user_apikey, self.full_execution["execution_id"]), - "frontend_abort": "%s/workflows/%s/run?authorization=%s&reference_execution=%s&answer=false" % (frontend_url, self.full_execution["workflow"]["id"], user_apikey, self.full_execution["execution_id"]), - "api_continue": "%s/api/v1/workflows/%s/execute?authorization=%s&reference_execution=%s&answer=true" % (frontend_url, self.full_execution["workflow"]["id"], user_apikey, self.full_execution["execution_id"]), - "api_abort": "%s/api/v1/workflows/%s/execute?authorization=%s&reference_execution=%s&answer=false" % (frontend_url, self.full_execution["workflow"]["id"], user_apikey, self.full_execution["execution_id"]), + "frontend_continue": "%s/workflows/%s/run?authorization=%s&reference_execution=%s&answer=true" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"]), + "frontend_abort": "%s/workflows/%s/run?authorization=%s&reference_execution=%s&answer=false" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"]), + "api_continue": "%s/api/v1/workflows/%s/execute?authorization=%s&reference_execution=%s&answer=true" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"]), + "api_abort": "%s/api/v1/workflows/%s/execute?authorization=%s&reference_execution=%s&answer=false" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"]), }) ret = self.run_subflow(user_apikey, item, argument, source_workflow=self.full_execution["workflow"]["id"], source_execution=self.full_execution["execution_id"], source_auth=self.full_execution["authorization"], startnode=startnode, backend_url=backend_url, source_node=source_node) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 9e656b0f..57c29779 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -971,10 +971,16 @@ def get_file_value(self, filedata): try: return filedata["data"].decode("utf-16") except: - return { - "success": False, - "reason": "Got the file, but the encoding can't be printed", - } + try: + return filedata["data"].decode("utf-8") + except: + try: + return filedata["data"].decode("latin-1") + except: + return { + "success": False, + "reason": "Got the file, but the encoding can't be printed", + } def download_remote_file(self, url, custom_filename=""): ret = requests.get(url, verify=False) # nosec From d21b76fe5d30fb4cc890bc4c025bc2d0063c2073 Mon Sep 17 00:00:00 2001 From: dhaval055 Date: Wed, 17 May 2023 15:03:26 +0530 Subject: [PATCH 031/259] added action to manage user in group --- active-directory/1.0.0/api.yaml | 55 +++++++++++++++++++++++++++++++ active-directory/1.0.0/src/app.py | 53 +++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+) diff --git a/active-directory/1.0.0/api.yaml b/active-directory/1.0.0/api.yaml index f9c6a3d6..187ed2eb 100644 --- a/active-directory/1.0.0/api.yaml +++ b/active-directory/1.0.0/api.yaml @@ -249,4 +249,59 @@ actions: returns: schema: type: string + - name: add_user_to_group + description: Add user to group + parameters: + - name: samaccountname + description: user to change password for + required: true + multiline: false + example: 'user01' + schema: + type: string + - name: search_base + description: "If empty it will use the base_dn." + required: false + multiline: false + example: "OU=Users,DC=icplahd,DC=com" + schema: + type: string + - name: group_name + description: "Group you want to add user to" + required: true + multiline: false + example: "Group name" + schema: + type: string + returns: + schema: + type: string + - name: remove_user_from_group + description: Remove user from group + parameters: + - name: samaccountname + description: user to change password for + required: true + multiline: false + example: 'user01' + schema: + type: string + - name: search_base + description: "If empty it will use the base_dn." + required: false + multiline: false + example: "OU=Users,DC=icplahd,DC=com" + schema: + type: string + - name: group_name + description: "Group you want to remove user from" + required: true + multiline: false + example: "Group name" + schema: + type: string + returns: + schema: + type: string + large_image: data:image/png;base64,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 diff --git a/active-directory/1.0.0/src/app.py b/active-directory/1.0.0/src/app.py index 5d1dd114..3157dfdc 100644 --- a/active-directory/1.0.0/src/app.py +++ b/active-directory/1.0.0/src/app.py @@ -7,6 +7,9 @@ MODIFY_REPLACE, ALL_ATTRIBUTES, ) +from ldap3.extend.microsoft.addMembersToGroups import ad_add_members_to_groups as addUsersInGroups +from ldap3.extend.microsoft.removeMembersFromGroups import ad_remove_members_from_groups as removeUsersFromGroups + from walkoff_app_sdk.app_base import AppBase class ActiveDirectory(AppBase): @@ -425,6 +428,56 @@ def change_user_password_at_next_login(self,server,domain,port,login_user,passwo return result + def add_user_to_group(self, server, domain, port, login_user, password, base_dn, use_ssl, samaccountname, search_base, group_name): + + if search_base: + base_dn = search_base + + c = self.__ldap_connection(server, port, domain, login_user, password, use_ssl) + + c.search(base_dn, f"(SAMAccountName={samaccountname})") + if len(c.entries) == 0: + return {"success":"false","message":f"User {samaccountname} not found"} + user_dn = c.entries[0].entry_dn + + search_filter = f'(&(objectClass=group)(cn={group_name}))' + c.search(base_dn, search_filter, attributes=["distinguishedName"]) + if len(c.entries) == 0: + return {"success":"false","message":f"Group {group_name} not found"} + group_dn = c.entries[0]["distinguishedName"] + print(group_dn) + + res = addUsersInGroups(c, user_dn, str(group_dn),fix=True) + if res == True: + return {"success":"true","message":f"User {samaccountname} was added to group {group_name}"} + else: + return {"success":"false","message":f"Could not add user to group"} + + def remove_user_from_group(self, server, domain, port, login_user, password, base_dn, use_ssl, samaccountname, search_base, group_name): + + if search_base: + base_dn = search_base + + c = self.__ldap_connection(server, port, domain, login_user, password, use_ssl) + + c.search(base_dn, f"(SAMAccountName={samaccountname})") + if len(c.entries) == 0: + return {"success":"false","message":f"User {samaccountname} not found"} + user_dn = c.entries[0].entry_dn + + search_filter = f'(&(objectClass=group)(cn={group_name}))' + c.search(base_dn, search_filter, attributes=["distinguishedName"]) + if len(c.entries) == 0: + return {"success":"false","message":f"Group {group_name} not found"} + group_dn = c.entries[0]["distinguishedName"] + print(group_dn) + + res = removeUsersFromGroups(c, user_dn, str(group_dn),fix=True) + if res == True: + return {"success":"true","message":f"User {samaccountname} was removed from group {group_name}"} + else: + return {"success":"false","message":f"Could not remove user to group"} + if __name__ == "__main__": ActiveDirectory.run() From ac8b1339cb77b3d38fac3737ebe5065caa088bde Mon Sep 17 00:00:00 2001 From: Dhaval Dave Date: Wed, 7 Jun 2023 12:33:52 +0530 Subject: [PATCH 032/259] Update app.py --- shuffle-tools/1.2.0/src/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 57c29779..6d81c64b 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -2390,7 +2390,7 @@ def run_ssh_command(self, host, port, user_name, private_key_file_id, password, except Exception as e: return {"success":"false","message":str(e)} - return {"success":"true","output": stdout.read().decode()} + return {"success":"true","output": stdout.read().decode(errors='ignore')} if __name__ == "__main__": From 5c8bc5954e1befc6432819ca3428f212c815d9e2 Mon Sep 17 00:00:00 2001 From: EntwicklungsLeiter <50797003+Entwicklungsleiter@users.noreply.github.com> Date: Tue, 6 Jun 2023 15:21:57 +0200 Subject: [PATCH 033/259] adding first version of twilio Python app for Shuffle --- twilio/1.3.0/Dockerfile | 27 +++++ twilio/1.3.0/api.yaml | 107 +++++++++++++++++++ twilio/1.3.0/requirements.txt | 2 + twilio/1.3.0/src/app.py | 189 ++++++++++++++++++++++++++++++++++ 4 files changed, 325 insertions(+) create mode 100644 twilio/1.3.0/Dockerfile create mode 100644 twilio/1.3.0/api.yaml create mode 100644 twilio/1.3.0/requirements.txt create mode 100755 twilio/1.3.0/src/app.py diff --git a/twilio/1.3.0/Dockerfile b/twilio/1.3.0/Dockerfile new file mode 100644 index 00000000..9bbc5110 --- /dev/null +++ b/twilio/1.3.0/Dockerfile @@ -0,0 +1,27 @@ +# Base our app image off of the WALKOFF App SDK image +FROM frikky/shuffle:app_sdk as base + +# We're going to stage away all of the bloat from the build tools so lets create a builder stage +FROM base as builder + +# Install all alpine build tools needed for our pip installs +RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev + +# Install all of our pip packages in a single directory that we can copy to our base image later +RUN mkdir /install +WORKDIR /install +COPY requirements.txt /requirements.txt +RUN pip install --prefix="/install" -r /requirements.txt + +# Switch back to our base image and copy in all of our built packages and source code +FROM base +COPY --from=builder /install /usr/local +COPY src /app +RUN apk add curl + +# Install any binary dependencies needed in our final image +# RUN apk --no-cache add --update my_binary_dependency + +# Finally, lets run our app! +WORKDIR /app +CMD python app.py --log-level DEBUG diff --git a/twilio/1.3.0/api.yaml b/twilio/1.3.0/api.yaml new file mode 100644 index 00000000..e4c99826 --- /dev/null +++ b/twilio/1.3.0/api.yaml @@ -0,0 +1,107 @@ +walkoff_version: 1.3.0 +app_version: 2.1.0 +name: twilio +description: Send SMS through Twilio +tags: + - HTTP +categories: + - HTTP +contact_info: + name: "Stefan Rank-Kunitz" + url: https://www.digifors.de/ + email: "stefan.rank-kunitz@digifors.de" +authentication: + required: true + parameters: + - name: url + description: Twilio API URL. + example: "https://api.twilio.com/2010-04-01/Accounts/TWILIO_ACCOUNT_SID" + required: true + schema: + type: string + - name: username + description: The username to use + multiline: false + required: true + example: "Username" + schema: + type: string + - name: password + description: The password to use + multiline: false + required: true + example: "*****" + schema: + type: string + - name: headers + description: Headers to use + multiline: true + required: false + example: "Content-Type: application/x-www-form-urlencoded" + schema: + type: string +actions: + - name: Send_SMS + description: sends an SMS to Twilio API endpoint + parameters: + - name: url + description: Twilio API URL + multiline: false + example: "https://api.twilio.com/2010-04-01/Accounts/TWILIO_ACCOUNT_SID" + required: true + schema: + type: string + - name: headers + description: Headers to use + multiline: true + required: false + example: "Content-Type: application/x-www-form-urlencoded" + schema: + type: string + - name: username + description: The username to use + multiline: false + required: true + example: "Username" + schema: + type: string + - name: password + description: The password to use + multiline: false + required: true + example: "*****" + schema: + type: string + - name: timeout + description: Add a timeout for the request, in seconds + multiline: false + required: false + example: "10" + schema: + type: bool + - name: body + description: The message to send. + multiline: true + example: "I did not have any sexual relationship with Miss Lewinsky!" + required: true + schema: + type: string + - name: from + description: The senders phone number. + multiline: false + example: "+1234567890" + required: true + schema: + type: string + - name: to + description: The message receiver phone number. + multiline: false + example: "+9876543210" + required: true + schema: + type: string + returns: + schema: + type: string + example: "404 NOT FOUND" +large_image: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADkAAAA5CAYAAACMGIOFAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyNpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NDkxMSwgMjAxMy8xMC8yOS0xMTo0NzoxNiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChNYWNpbnRvc2gpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjREOTUwQkUyQUQwNDExRTM4QUY0RDlBODA0OUZGNENBIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjREOTUwQkUzQUQwNDExRTM4QUY0RDlBODA0OUZGNENBIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NEQ5NTBCRTBBRDA0MTFFMzhBRjREOUE4MDQ5RkY0Q0EiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NEQ5NTBCRTFBRDA0MTFFMzhBRjREOUE4MDQ5RkY0Q0EiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4jmx8hAAAGI0lEQVR42uRbXUwdRRReLlh5aCrUEFNNrJZaNCAPRmwR+pMKNZEaq1KTIgZNsQ819KkhYnw01jTVh5rUBGmUiJgo8SdRHwpNCKmWloYHCqmgBeoDxhBLJX2oKNHv6HfJZrN7ZmbvD4ae5Mtkd+fMnO/OzDlnZvfmeBmQ34pLNqLYAZQD9wP3AkXAaiAXWASuA7PAFPADMAL03355/Kd025OTRmIVKBqBPcDdKTT1M/Al0AXCQ8tOEsTySOwwUJqBSTEGHCPhv7JKEuRErwF4A7jHy7xMA68D3SD7d8ZJguADKN4DtnvZl37gIIheclFKOBJsRjG8TAQ9OrNh2pHekUSjq1Cc5Pr7v0gXsB+jupAySRBcg+Jz4LGYxvwOjAOXgXngBpAPSLvFQAlwW8y2TwPPgOh8bJIk2As84tj5WeBToE88pOYs6MTEM9cAzwGVjn2dB2o1ojmGKfqtwwj+CXSKy0eH4ymEpRKGpCbgFocRfSJq6mokP3JYg18Dh9DJVBqTC8mSjgO7bdco+n/BmiS91/sWDUtqdgCNf5Ip7wJb9qFoZ0pokpdhS4eRJOPgMJ2DJuJI6lKZmo5T+Bs6Kk3EqT0UjKOJECdwwoLgKFCVDYIi7KeK/Woidp8gj8hkoIEB1zSCNej412wGRfZXw/5NCUND6HRlsv2jIReVNfhw2Ajy1xP3vxVYC8wAp2xSMC6Rx4F1wFVgABgMCz2cuhcMa1Ry3fuSSb2f5IsoPjDY0xDmZNixhI/NITo9dE5zIXqFdCr1IXqDEkagNxHhjLoNtr4E3Q+DJC+iKNPCBJSejCD4PUcvSqTtan/AZqLxnaFPGdXKCKIStuo0vwG9B5fWJDe8ZYZAfyhiinYaCIpIZ0cC994y9Omx3c6gI6G00K4oKSOvJcdjCvqdEYG+MmKKhkkzRy85ivst9bYQQUc0xR9Yk0Y/yacMlY9F3N/q4CBX+XLgzby2lW2Odnl+XgkeOq3Xkm0lHq713KQwULrqhcXPs4reeuGXsIiLnynPZhyN/SWm3kxM+/6Nm0Ky3FCpV3l2ysHQa9wWiQzx2lZ6Yz4TKU9w06pteMeULOQS46CNHE1uhVD+IdeWej2GhGKMdkZJiZDcoFSYsDgdO8A4qMkXIaTk+iuD3kW2r6V7Yt+EUmWDkCxSKhhPs5nJVDOxXwiZoq8Be1FvMaAn18/yeXDqLrC96rBMydHOojxDDjhvmTxLvVfgydoYJgrpZM5rB00kegR676CsYO46R715hzWr1V0tJHMN+zOXncI8z3VcdxiyRs+ksEnR7MwVkosK0XyXnpjJ+EdyiARMeremOJKanYt53D5FHQmucSAnuWlzIJO5hmdH6VkXQ/Tkx20lCvxrEs/kGKPNkqxm53VxPLNKhY0WBAs51Q6GpGpi+JsSsEkoSLCHzwtCUkBp7wzbN4lm56yQnFQqbIrYAfilnbsMTZ7maPlFrvdY7F7aDT+y2LdJqTIpJLVzGpnGpYYdfb3lumnlWW5yDbZa6tWznygp9fQT+HEhOWLopFZ5tsvBORT4diEVIVM0rg21Bt0RIdlvqLRXeXano6tfF1Pvrpj2ifQn+I7+ilKpkkccUccTLjIXKG3lasRyKfH0dydXhF9y02zKIQ9H3B9wMHTBtws5F5ICajLgaJfn55Uk2WWo3MR3E2EnaucsDe1IxjyWJy31BongKIo9TQbdriWS/MpCO52Wt0vHI3YATRbTVnYTbYF7r3rmE3FptyliJ/Sup7/1Gk1+PeI/QX/b0OFunneGHUE8qoyoBPztwcyF19uU/aiMXqVy7lpnsHeJT7pP0LfQ8Dgn6LvodTN3gs4GnrdYn/Iuoirb70Jo3x3efwfSprdbjbDv4+RF8IVPt0XclA762GG2CfZZEOz3Aq8Qbr73k77DqRaLvqXDC2HOKM0E93ENFltUbwnzATfvNwPsZOV//eHb8a/c73gCRFfuF1mBqbtyv60LkG1mzpi/jORu0It22CrE/d5VTrd3LANBCfSZ/d7VF0d3cupOZ4ncNPvb6Uow1kgGRjXT36CPcjeR/W/QIwivzH8TKIT9/wuRECGvB6P+FzLJEJOx/4X8I8AAXH2Fd+613pMAAAAASUVORK5CYII= diff --git a/twilio/1.3.0/requirements.txt b/twilio/1.3.0/requirements.txt new file mode 100644 index 00000000..ae3e5391 --- /dev/null +++ b/twilio/1.3.0/requirements.txt @@ -0,0 +1,2 @@ +uncurl==0.0.10 +requests==2.25.1 \ No newline at end of file diff --git a/twilio/1.3.0/src/app.py b/twilio/1.3.0/src/app.py new file mode 100755 index 00000000..72e54156 --- /dev/null +++ b/twilio/1.3.0/src/app.py @@ -0,0 +1,189 @@ +import time +import json +import ast +import random +import socket +import uncurl +import asyncio +import requests +import subprocess + +from walkoff_app_sdk.app_base import AppBase + +class TWILIO(AppBase): + __version__ = "1.3.0" + app_name = "twilio" + + def __init__(self, redis, logger, console_logger=None): + print("INIT") + """ + Each app should have this __init__ to set up Redis and logging. + :param redis: + :param logger: + :param console_logger: + """ + super().__init__(redis, logger, console_logger) + + def splitheaders(self, headers): + parsed_headers = {} + if headers: + split_headers = headers.split("\n") + self.logger.info(split_headers) + for header in split_headers: + if ": " in header: + splititem = ": " + elif ":" in header: + splititem = ":" + elif "= " in header: + splititem = "= " + elif "=" in header: + splititem = "=" + else: + self.logger.info("Skipping header %s as its invalid" % header) + continue + + splitheader = header.split(splititem) + if len(splitheader) == 2: + parsed_headers[splitheader[0]] = splitheader[1] + else: + self.logger.info("Skipping header %s with split %s cus only one item" % (header, splititem)) + continue + + return parsed_headers + + def checkbody(self, body): + # Indicates json + if isinstance(body, str): + if body.strip().startswith("{"): + body = json.dumps(ast.literal_eval(body)) + + + # Not sure if loading is necessary + # Seemed to work with plain string into data=body too, and not parsed json=body + #try: + # body = json.loads(body) + #except json.decoder.JSONDecodeError as e: + # return body + + return body + else: + return body + + if isinstance(body, dict) or isinstance(body, list): + try: + body = json.dumps(body) + except: + return body + + return body + + def fix_url(self, url): + # Random bugs seen by users + if "hhttp" in url: + url = url.replace("hhttp", "http") + + if "http:/" in url and not "http://" in url: + url = url.replace("http:/", "http://", -1) + if "https:/" in url and not "https://" in url: + url = url.replace("https:/", "https://", -1) + if "http:///" in url: + url = url.replace("http:///", "http://", -1) + if "https:///" in url: + url = url.replace("https:///", "https://", -1) + if not "http://" in url and not "http" in url: + url = f"http://{url}" + + return url + + def return_file(self, requestdata): + filedata = { + "filename": "response.txt", + "data": requestdata, + } + fileret = self.set_files([filedata]) + if len(fileret) == 1: + return {"success": True, "file_id": fileret[0]} + + return fileret + + def prepare_response(self, request): + try: + parsedheaders = {} + for key, value in request.headers.items(): + parsedheaders[key] = value + + cookies = {} + if request.cookies: + for key, value in request.cookies.items(): + cookies[key] = value + + + jsondata = request.text + try: + jsondata = json.loads(jsondata) + except: + pass + + return json.dumps({ + "success": True, + "status": request.status_code, + "url": request.url, + "headers": parsedheaders, + "body": jsondata, + "cookies":cookies, + }) + except Exception as e: + print(f"[WARNING] Failed in request: {e}") + return request.text + + + def POST(self, url, headers="", body="", username="", password="", timeout=5, to_file=False): + url = self.fix_url(url) + + parsed_headers = self.splitheaders(headers) + parsed_headers["User-Agent"] = "Shuffle Automation" + body = self.checkbody(body) + + auth=None + if username or password: + # Shouldn't be used if authorization headers exist + if "Authorization" in parsed_headers: + #print("Found authorization - skipping username & pw") + pass + else: + auth = requests.auth.HTTPBasicAuth(username, password) + + if not timeout: + timeout = 5 + if timeout: + timeout = int(timeout) + + if to_file == "true": + to_file = True + else: + to_file = False + + request = requests.post(url, headers=parsed_headers, auth=auth, data=body, timeout=timeout) + if not to_file: + return self.prepare_response(request) + + return self.return_file(request.text) + + +# Run the actual thing after we've checked params +def run(request): + print("Starting cloud!") + action = request.get_json() + print(action) + print(type(action)) + authorization_key = action.get("authorization") + current_execution_id = action.get("execution_id") + + if action and "name" in action and "app_name" in action: + TWILIO.run(action) + return f'Attempting to execute function {action["name"]} in app {action["app_name"]}' + else: + return f'Invalid action' + +if __name__ == "__main__": + TWILIO.run() From 6bd9249dcbf3337767fbcb494e9f0aeb6693490f Mon Sep 17 00:00:00 2001 From: EntwicklungsLeiter <50797003+Entwicklungsleiter@users.noreply.github.com> Date: Tue, 6 Jun 2023 16:18:09 +0200 Subject: [PATCH 034/259] making app version same in all files --- twilio/1.3.0/api.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twilio/1.3.0/api.yaml b/twilio/1.3.0/api.yaml index e4c99826..0ad36e07 100644 --- a/twilio/1.3.0/api.yaml +++ b/twilio/1.3.0/api.yaml @@ -1,5 +1,5 @@ walkoff_version: 1.3.0 -app_version: 2.1.0 +app_version: 1.3.0 name: twilio description: Send SMS through Twilio tags: From e3e5555ef031ab22c6ecfde3337155177eabf9c7 Mon Sep 17 00:00:00 2001 From: EntwicklungsLeiter <50797003+Entwicklungsleiter@users.noreply.github.com> Date: Tue, 6 Jun 2023 16:20:23 +0200 Subject: [PATCH 035/259] fix method name --- twilio/1.3.0/src/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twilio/1.3.0/src/app.py b/twilio/1.3.0/src/app.py index 72e54156..21c3e510 100755 --- a/twilio/1.3.0/src/app.py +++ b/twilio/1.3.0/src/app.py @@ -137,7 +137,7 @@ def prepare_response(self, request): return request.text - def POST(self, url, headers="", body="", username="", password="", timeout=5, to_file=False): + def Send_SMS(self, url, headers="", body="", username="", password="", timeout=5, to_file=False): url = self.fix_url(url) parsed_headers = self.splitheaders(headers) From d32270bd8d91b81b72aab7160bda21e526ca2496 Mon Sep 17 00:00:00 2001 From: EntwicklungsLeiter <50797003+Entwicklungsleiter@users.noreply.github.com> Date: Tue, 6 Jun 2023 16:34:21 +0200 Subject: [PATCH 036/259] increase version number to force reload in Shuffle --- twilio/{1.3.0 => 1.4.0}/Dockerfile | 0 twilio/{1.3.0 => 1.4.0}/api.yaml | 4 ++-- twilio/{1.3.0 => 1.4.0}/requirements.txt | 0 twilio/{1.3.0 => 1.4.0}/src/app.py | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename twilio/{1.3.0 => 1.4.0}/Dockerfile (100%) rename twilio/{1.3.0 => 1.4.0}/api.yaml (99%) rename twilio/{1.3.0 => 1.4.0}/requirements.txt (100%) rename twilio/{1.3.0 => 1.4.0}/src/app.py (99%) diff --git a/twilio/1.3.0/Dockerfile b/twilio/1.4.0/Dockerfile similarity index 100% rename from twilio/1.3.0/Dockerfile rename to twilio/1.4.0/Dockerfile diff --git a/twilio/1.3.0/api.yaml b/twilio/1.4.0/api.yaml similarity index 99% rename from twilio/1.3.0/api.yaml rename to twilio/1.4.0/api.yaml index 0ad36e07..f20e15ed 100644 --- a/twilio/1.3.0/api.yaml +++ b/twilio/1.4.0/api.yaml @@ -1,5 +1,5 @@ -walkoff_version: 1.3.0 -app_version: 1.3.0 +walkoff_version: 1.4.0 +app_version: 1.4.0 name: twilio description: Send SMS through Twilio tags: diff --git a/twilio/1.3.0/requirements.txt b/twilio/1.4.0/requirements.txt similarity index 100% rename from twilio/1.3.0/requirements.txt rename to twilio/1.4.0/requirements.txt diff --git a/twilio/1.3.0/src/app.py b/twilio/1.4.0/src/app.py similarity index 99% rename from twilio/1.3.0/src/app.py rename to twilio/1.4.0/src/app.py index 21c3e510..f09f8d2d 100755 --- a/twilio/1.3.0/src/app.py +++ b/twilio/1.4.0/src/app.py @@ -11,7 +11,7 @@ from walkoff_app_sdk.app_base import AppBase class TWILIO(AppBase): - __version__ = "1.3.0" + __version__ = "1.4.0" app_name = "twilio" def __init__(self, redis, logger, console_logger=None): From 02ccdd958989e367cc4007dba607192a1726c246 Mon Sep 17 00:00:00 2001 From: EntwicklungsLeiter <50797003+Entwicklungsleiter@users.noreply.github.com> Date: Tue, 6 Jun 2023 17:14:16 +0200 Subject: [PATCH 037/259] fix: send multiple POST data with python request.post method --- twilio/{1.4.0 => 1.5.0}/Dockerfile | 0 twilio/{1.4.0 => 1.5.0}/api.yaml | 10 +++++----- twilio/{1.4.0 => 1.5.0}/requirements.txt | 0 twilio/{1.4.0 => 1.5.0}/src/app.py | 8 +++++--- 4 files changed, 10 insertions(+), 8 deletions(-) rename twilio/{1.4.0 => 1.5.0}/Dockerfile (100%) rename twilio/{1.4.0 => 1.5.0}/api.yaml (97%) rename twilio/{1.4.0 => 1.5.0}/requirements.txt (100%) rename twilio/{1.4.0 => 1.5.0}/src/app.py (95%) diff --git a/twilio/1.4.0/Dockerfile b/twilio/1.5.0/Dockerfile similarity index 100% rename from twilio/1.4.0/Dockerfile rename to twilio/1.5.0/Dockerfile diff --git a/twilio/1.4.0/api.yaml b/twilio/1.5.0/api.yaml similarity index 97% rename from twilio/1.4.0/api.yaml rename to twilio/1.5.0/api.yaml index f20e15ed..a363d98b 100644 --- a/twilio/1.4.0/api.yaml +++ b/twilio/1.5.0/api.yaml @@ -1,5 +1,5 @@ -walkoff_version: 1.4.0 -app_version: 1.4.0 +walkoff_version: 1.5.0 +app_version: 1.5.0 name: twilio description: Send SMS through Twilio tags: @@ -15,19 +15,19 @@ authentication: parameters: - name: url description: Twilio API URL. - example: "https://api.twilio.com/2010-04-01/Accounts/TWILIO_ACCOUNT_SID" + example: "https://api.twilio.com/2010-04-01/Accounts/TWILIO_ACCOUNT_SID/Messages.json" required: true schema: type: string - name: username - description: The username to use + description: Your Twilio account SID multiline: false required: true example: "Username" schema: type: string - name: password - description: The password to use + description: Your Twilio account secret multiline: false required: true example: "*****" diff --git a/twilio/1.4.0/requirements.txt b/twilio/1.5.0/requirements.txt similarity index 100% rename from twilio/1.4.0/requirements.txt rename to twilio/1.5.0/requirements.txt diff --git a/twilio/1.4.0/src/app.py b/twilio/1.5.0/src/app.py similarity index 95% rename from twilio/1.4.0/src/app.py rename to twilio/1.5.0/src/app.py index f09f8d2d..c1fcaac6 100755 --- a/twilio/1.4.0/src/app.py +++ b/twilio/1.5.0/src/app.py @@ -11,7 +11,7 @@ from walkoff_app_sdk.app_base import AppBase class TWILIO(AppBase): - __version__ = "1.4.0" + __version__ = "1.5.0" app_name = "twilio" def __init__(self, redis, logger, console_logger=None): @@ -137,13 +137,15 @@ def prepare_response(self, request): return request.text - def Send_SMS(self, url, headers="", body="", username="", password="", timeout=5, to_file=False): + def Send_SMS(self, url, headers="", username="", password="", body="", From="", To="", timeout=5, to_file=False): url = self.fix_url(url) parsed_headers = self.splitheaders(headers) parsed_headers["User-Agent"] = "Shuffle Automation" body = self.checkbody(body) + data = {'Body' : body, 'From' : From, 'To' : To} + auth=None if username or password: # Shouldn't be used if authorization headers exist @@ -163,7 +165,7 @@ def Send_SMS(self, url, headers="", body="", username="", password="", timeout=5 else: to_file = False - request = requests.post(url, headers=parsed_headers, auth=auth, data=body, timeout=timeout) + request = requests.post(url, headers=parsed_headers, auth=auth, data=data, timeout=timeout) if not to_file: return self.prepare_response(request) From e983288b861c00fc068496ad28f9b9a9d84881f4 Mon Sep 17 00:00:00 2001 From: EntwicklungsLeiter <50797003+Entwicklungsleiter@users.noreply.github.com> Date: Tue, 6 Jun 2023 18:24:19 +0200 Subject: [PATCH 038/259] fix: variable names are case sensitive --- twilio/{1.5.0 => 1.6.0}/Dockerfile | 0 twilio/{1.5.0 => 1.6.0}/api.yaml | 8 ++++---- twilio/{1.5.0 => 1.6.0}/requirements.txt | 0 twilio/{1.5.0 => 1.6.0}/src/app.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) rename twilio/{1.5.0 => 1.6.0}/Dockerfile (100%) rename twilio/{1.5.0 => 1.6.0}/api.yaml (98%) rename twilio/{1.5.0 => 1.6.0}/requirements.txt (100%) rename twilio/{1.5.0 => 1.6.0}/src/app.py (99%) diff --git a/twilio/1.5.0/Dockerfile b/twilio/1.6.0/Dockerfile similarity index 100% rename from twilio/1.5.0/Dockerfile rename to twilio/1.6.0/Dockerfile diff --git a/twilio/1.5.0/api.yaml b/twilio/1.6.0/api.yaml similarity index 98% rename from twilio/1.5.0/api.yaml rename to twilio/1.6.0/api.yaml index a363d98b..09db1ac4 100644 --- a/twilio/1.5.0/api.yaml +++ b/twilio/1.6.0/api.yaml @@ -1,5 +1,5 @@ -walkoff_version: 1.5.0 -app_version: 1.5.0 +walkoff_version: 1.6.0 +app_version: 1.6.0 name: twilio description: Send SMS through Twilio tags: @@ -86,14 +86,14 @@ actions: required: true schema: type: string - - name: from + - name: From description: The senders phone number. multiline: false example: "+1234567890" required: true schema: type: string - - name: to + - name: To description: The message receiver phone number. multiline: false example: "+9876543210" diff --git a/twilio/1.5.0/requirements.txt b/twilio/1.6.0/requirements.txt similarity index 100% rename from twilio/1.5.0/requirements.txt rename to twilio/1.6.0/requirements.txt diff --git a/twilio/1.5.0/src/app.py b/twilio/1.6.0/src/app.py similarity index 99% rename from twilio/1.5.0/src/app.py rename to twilio/1.6.0/src/app.py index c1fcaac6..a4c79960 100755 --- a/twilio/1.5.0/src/app.py +++ b/twilio/1.6.0/src/app.py @@ -11,7 +11,7 @@ from walkoff_app_sdk.app_base import AppBase class TWILIO(AppBase): - __version__ = "1.5.0" + __version__ = "1.6.0" app_name = "twilio" def __init__(self, redis, logger, console_logger=None): From df68562fb735da0573c68de51f622698c7f28ef6 Mon Sep 17 00:00:00 2001 From: EntwicklungsLeiter <50797003+Entwicklungsleiter@users.noreply.github.com> Date: Wed, 7 Jun 2023 10:25:55 +0200 Subject: [PATCH 039/259] making "To" phone number of SMS receiver be multiple numbers as comma separated list --- twilio/{1.6.0 => 1.7.0}/Dockerfile | 0 twilio/{1.6.0 => 1.7.0}/api.yaml | 20 ++++---- twilio/{1.6.0 => 1.7.0}/requirements.txt | 1 - twilio/{1.6.0 => 1.7.0}/src/app.py | 62 ++++++++++++++---------- 4 files changed, 47 insertions(+), 36 deletions(-) rename twilio/{1.6.0 => 1.7.0}/Dockerfile (100%) rename twilio/{1.6.0 => 1.7.0}/api.yaml (91%) rename twilio/{1.6.0 => 1.7.0}/requirements.txt (51%) rename twilio/{1.6.0 => 1.7.0}/src/app.py (79%) diff --git a/twilio/1.6.0/Dockerfile b/twilio/1.7.0/Dockerfile similarity index 100% rename from twilio/1.6.0/Dockerfile rename to twilio/1.7.0/Dockerfile diff --git a/twilio/1.6.0/api.yaml b/twilio/1.7.0/api.yaml similarity index 91% rename from twilio/1.6.0/api.yaml rename to twilio/1.7.0/api.yaml index 09db1ac4..65af9320 100644 --- a/twilio/1.6.0/api.yaml +++ b/twilio/1.7.0/api.yaml @@ -1,7 +1,7 @@ -walkoff_version: 1.6.0 -app_version: 1.6.0 +walkoff_version: 1.7.0 +app_version: 1.7.0 name: twilio -description: Send SMS through Twilio +description: Send SMS from Shuffle through Twilio.com tags: - HTTP categories: @@ -47,7 +47,7 @@ actions: - name: url description: Twilio API URL multiline: false - example: "https://api.twilio.com/2010-04-01/Accounts/TWILIO_ACCOUNT_SID" + example: "https://api.twilio.com/2010-04-01/Accounts/TWILIO_ACCOUNT_SID/Messages.json" required: true schema: type: string @@ -59,21 +59,21 @@ actions: schema: type: string - name: username - description: The username to use + description: Your Twilio account SID multiline: false required: true example: "Username" schema: type: string - name: password - description: The password to use + description: Your Twilio account secret multiline: false required: true example: "*****" schema: type: string - name: timeout - description: Add a timeout for the request, in seconds + description: Add a timeout (in seconds) for the request multiline: false required: false example: "10" @@ -87,16 +87,16 @@ actions: schema: type: string - name: From - description: The senders phone number. + description: The senders phone number, see Your Twilio account for accepted phone numbers. multiline: false example: "+1234567890" required: true schema: type: string - name: To - description: The message receiver phone number. + description: The message receiver phone number (or a comma separated list of phone numbers). multiline: false - example: "+9876543210" + example: "+9876543210,+1928374650" required: true schema: type: string diff --git a/twilio/1.6.0/requirements.txt b/twilio/1.7.0/requirements.txt similarity index 51% rename from twilio/1.6.0/requirements.txt rename to twilio/1.7.0/requirements.txt index ae3e5391..fd7d3e06 100644 --- a/twilio/1.6.0/requirements.txt +++ b/twilio/1.7.0/requirements.txt @@ -1,2 +1 @@ -uncurl==0.0.10 requests==2.25.1 \ No newline at end of file diff --git a/twilio/1.6.0/src/app.py b/twilio/1.7.0/src/app.py similarity index 79% rename from twilio/1.6.0/src/app.py rename to twilio/1.7.0/src/app.py index a4c79960..524db127 100755 --- a/twilio/1.6.0/src/app.py +++ b/twilio/1.7.0/src/app.py @@ -1,17 +1,11 @@ -import time import json import ast -import random -import socket -import uncurl -import asyncio import requests -import subprocess from walkoff_app_sdk.app_base import AppBase class TWILIO(AppBase): - __version__ = "1.6.0" + __version__ = "1.7.0" app_name = "twilio" def __init__(self, redis, logger, console_logger=None): @@ -124,28 +118,44 @@ def prepare_response(self, request): except: pass - return json.dumps({ + return { "success": True, "status": request.status_code, "url": request.url, "headers": parsedheaders, "body": jsondata, "cookies":cookies, - }) + } except Exception as e: print(f"[WARNING] Failed in request: {e}") - return request.text + return { + "success": False, + "status": "XXX", + "error": request.text + } - def Send_SMS(self, url, headers="", username="", password="", body="", From="", To="", timeout=5, to_file=False): + def summarize_responses(one_response, summary): + summary["results"].append(one_response) + + # if ONE request fails, summary is marked as failed + if False == one_response["success"]: + summary["success"] = False + + # if one status code is not 200, use this failure status code for summary + if "200" != one_response["status"]: + summary["status"] = one_response["status"] + + return summary + + + def Send_SMS(self, url, headers="", username="", password="", body="", From="", To="", timeout=5): url = self.fix_url(url) parsed_headers = self.splitheaders(headers) parsed_headers["User-Agent"] = "Shuffle Automation" body = self.checkbody(body) - data = {'Body' : body, 'From' : From, 'To' : To} - auth=None if username or password: # Shouldn't be used if authorization headers exist @@ -155,21 +165,23 @@ def Send_SMS(self, url, headers="", username="", password="", body="", From="", else: auth = requests.auth.HTTPBasicAuth(username, password) - if not timeout: - timeout = 5 - if timeout: - timeout = int(timeout) + timeout = int(timeout) - if to_file == "true": - to_file = True - else: - to_file = False + summary = { + "success": True, + "status": "200", + "url": url, + "results": [] + } - request = requests.post(url, headers=parsed_headers, auth=auth, data=data, timeout=timeout) - if not to_file: - return self.prepare_response(request) + # send Twilio API request for every single receiver number + for receiver in To.split(","): + data = {'Body' : body, 'From' : From, 'To' : receiver.strip()} + request = requests.post(url, headers=parsed_headers, auth=auth, data=data, timeout=timeout) + response = self.prepare_response(request) + summary = self.summarize_responses(response, summary) - return self.return_file(request.text) + return json.dumps(summary) # Run the actual thing after we've checked params From d3707481d86964ff930b8537e197c302d35c83b9 Mon Sep 17 00:00:00 2001 From: EntwicklungsLeiter <50797003+Entwicklungsleiter@users.noreply.github.com> Date: Wed, 7 Jun 2023 12:46:23 +0200 Subject: [PATCH 040/259] bugfix in int() cast of boolean value --- twilio/{1.7.0 => 1.8.0}/Dockerfile | 0 twilio/{1.7.0 => 1.8.0}/api.yaml | 4 ++-- twilio/{1.7.0 => 1.8.0}/requirements.txt | 0 twilio/{1.7.0 => 1.8.0}/src/app.py | 7 +++++-- 4 files changed, 7 insertions(+), 4 deletions(-) rename twilio/{1.7.0 => 1.8.0}/Dockerfile (100%) rename twilio/{1.7.0 => 1.8.0}/api.yaml (99%) rename twilio/{1.7.0 => 1.8.0}/requirements.txt (100%) rename twilio/{1.7.0 => 1.8.0}/src/app.py (98%) diff --git a/twilio/1.7.0/Dockerfile b/twilio/1.8.0/Dockerfile similarity index 100% rename from twilio/1.7.0/Dockerfile rename to twilio/1.8.0/Dockerfile diff --git a/twilio/1.7.0/api.yaml b/twilio/1.8.0/api.yaml similarity index 99% rename from twilio/1.7.0/api.yaml rename to twilio/1.8.0/api.yaml index 65af9320..1947b876 100644 --- a/twilio/1.7.0/api.yaml +++ b/twilio/1.8.0/api.yaml @@ -1,5 +1,5 @@ -walkoff_version: 1.7.0 -app_version: 1.7.0 +walkoff_version: 1.8.0 +app_version: 1.8.0 name: twilio description: Send SMS from Shuffle through Twilio.com tags: diff --git a/twilio/1.7.0/requirements.txt b/twilio/1.8.0/requirements.txt similarity index 100% rename from twilio/1.7.0/requirements.txt rename to twilio/1.8.0/requirements.txt diff --git a/twilio/1.7.0/src/app.py b/twilio/1.8.0/src/app.py similarity index 98% rename from twilio/1.7.0/src/app.py rename to twilio/1.8.0/src/app.py index 524db127..f20a7238 100755 --- a/twilio/1.7.0/src/app.py +++ b/twilio/1.8.0/src/app.py @@ -5,7 +5,7 @@ from walkoff_app_sdk.app_base import AppBase class TWILIO(AppBase): - __version__ = "1.7.0" + __version__ = "1.8.0" app_name = "twilio" def __init__(self, redis, logger, console_logger=None): @@ -165,7 +165,10 @@ def Send_SMS(self, url, headers="", username="", password="", body="", From="", else: auth = requests.auth.HTTPBasicAuth(username, password) - timeout = int(timeout) + if not timeout: + timeout = 5 + if timeout: + timeout = int(timeout) summary = { "success": True, From a09554106fd8a60dcf4a45ac94350d064e5df969 Mon Sep 17 00:00:00 2001 From: EntwicklungsLeiter <50797003+Entwicklungsleiter@users.noreply.github.com> Date: Thu, 8 Jun 2023 08:33:31 +0200 Subject: [PATCH 041/259] making Twilio plugin for Shuffle open source --- twilio/1.8.0/api.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/twilio/1.8.0/api.yaml b/twilio/1.8.0/api.yaml index 1947b876..ea549d6c 100644 --- a/twilio/1.8.0/api.yaml +++ b/twilio/1.8.0/api.yaml @@ -7,9 +7,9 @@ tags: categories: - HTTP contact_info: - name: "Stefan Rank-Kunitz" - url: https://www.digifors.de/ - email: "stefan.rank-kunitz@digifors.de" + name: "Entwicklungsleiter" + url: https://github.com/Entwicklungsleiter + email: "50797003+Entwicklungsleiter@users.noreply.github.com" authentication: required: true parameters: From 00ab0b8a232e7d9bdaa3942c81a33a984c9e95e6 Mon Sep 17 00:00:00 2001 From: EntwicklungsLeiter <50797003+Entwicklungsleiter@users.noreply.github.com> Date: Mon, 12 Jun 2023 08:51:15 +0200 Subject: [PATCH 042/259] fix missing "self" as function argument --- twilio/{1.8.0 => 1.9.0}/Dockerfile | 0 twilio/{1.8.0 => 1.9.0}/api.yaml | 4 ++-- twilio/{1.8.0 => 1.9.0}/requirements.txt | 0 twilio/{1.8.0 => 1.9.0}/src/app.py | 4 ++-- 4 files changed, 4 insertions(+), 4 deletions(-) rename twilio/{1.8.0 => 1.9.0}/Dockerfile (100%) rename twilio/{1.8.0 => 1.9.0}/api.yaml (99%) rename twilio/{1.8.0 => 1.9.0}/requirements.txt (100%) rename twilio/{1.8.0 => 1.9.0}/src/app.py (98%) diff --git a/twilio/1.8.0/Dockerfile b/twilio/1.9.0/Dockerfile similarity index 100% rename from twilio/1.8.0/Dockerfile rename to twilio/1.9.0/Dockerfile diff --git a/twilio/1.8.0/api.yaml b/twilio/1.9.0/api.yaml similarity index 99% rename from twilio/1.8.0/api.yaml rename to twilio/1.9.0/api.yaml index ea549d6c..af084706 100644 --- a/twilio/1.8.0/api.yaml +++ b/twilio/1.9.0/api.yaml @@ -1,5 +1,5 @@ -walkoff_version: 1.8.0 -app_version: 1.8.0 +walkoff_version: 1.9.0 +app_version: 1.9.0 name: twilio description: Send SMS from Shuffle through Twilio.com tags: diff --git a/twilio/1.8.0/requirements.txt b/twilio/1.9.0/requirements.txt similarity index 100% rename from twilio/1.8.0/requirements.txt rename to twilio/1.9.0/requirements.txt diff --git a/twilio/1.8.0/src/app.py b/twilio/1.9.0/src/app.py similarity index 98% rename from twilio/1.8.0/src/app.py rename to twilio/1.9.0/src/app.py index f20a7238..d43533ff 100755 --- a/twilio/1.8.0/src/app.py +++ b/twilio/1.9.0/src/app.py @@ -5,7 +5,7 @@ from walkoff_app_sdk.app_base import AppBase class TWILIO(AppBase): - __version__ = "1.8.0" + __version__ = "1.9.0" app_name = "twilio" def __init__(self, redis, logger, console_logger=None): @@ -135,7 +135,7 @@ def prepare_response(self, request): } - def summarize_responses(one_response, summary): + def summarize_responses(self, one_response, summary): summary["results"].append(one_response) # if ONE request fails, summary is marked as failed From 985e380d2a58d6d7291660385775dfa0e54f511d Mon Sep 17 00:00:00 2001 From: Jay Gohil Date: Fri, 16 Jun 2023 16:43:19 +0530 Subject: [PATCH 043/259] Create project_automation.yml --- .github/workflows/project_automation.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/workflows/project_automation.yml diff --git a/.github/workflows/project_automation.yml b/.github/workflows/project_automation.yml new file mode 100644 index 00000000..0e3bf945 --- /dev/null +++ b/.github/workflows/project_automation.yml @@ -0,0 +1,16 @@ +name: Automation - Add all new issues to roadmap project + +on: + issues: + types: + - opened + +jobs: + add-to-project: + name: Add issue to project + runs-on: ubuntu-latest + steps: + - uses: actions/add-to-project@v0.5.0 + with: + project-url: https://github.com/orgs/Shuffle/projects/8 + github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} From b0dde8d62b25843cc4431761244b4f58ba271957 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Sat, 1 Jul 2023 06:54:59 +0530 Subject: [PATCH 044/259] Fixing #351: Overwriting the existing print() statement --- shuffle-tools/1.2.0/src/app.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 6d81c64b..fbdb1553 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -529,10 +529,14 @@ def execute_python(self, code): # 2. Subprocess execute file? try: f = StringIO() - with redirect_stdout(f): - exec(code) # nosec :( + def custom_print(*args, **kwargs): + return print(*args, file=f, **kwargs) + + with redirect_stdout(f): # just in case + exec(code, {"print": custom_print}) s = f.getvalue() + f.close() # why: https://www.youtube.com/watch?v=6SA6S9Ca5-U #try: # s = s.encode("utf-8") From 73e55121d19dbd47e63705dfe4ec6f59f7c7a381 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Tue, 4 Jul 2023 03:57:40 +0530 Subject: [PATCH 045/259] Making CI fix --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f06f556d..53e0cbd7 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -49,7 +49,7 @@ jobs: name: Login to DockerHub uses: docker/login-action@v1 with: - username: ${{ secrets.DOCKERHUB_USERNAME }} + username: ${{ secrets.DOCKERHUB_LOGIN_USER }} password: ${{ secrets.DOCKERHUB_TOKEN }} # Use below configuration for ghcr.io # with: From 4b132ed35b7ac9f7a3ef5876c0e0e72a72d72a21 Mon Sep 17 00:00:00 2001 From: frikky Date: Tue, 4 Jul 2023 10:23:27 +0200 Subject: [PATCH 046/259] Added globals and custom printing --- shuffle-tools/1.2.0/src/app.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index fbdb1553..13cb7b4b 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -532,8 +532,11 @@ def execute_python(self, code): def custom_print(*args, **kwargs): return print(*args, file=f, **kwargs) - with redirect_stdout(f): # just in case - exec(code, {"print": custom_print}) + #with redirect_stdout(f): # just in case + # Add globals in it too + globals_copy = globals().copy() + globals_copy["print"] = custom_print + exec(code, globals_copy) s = f.getvalue() f.close() # why: https://www.youtube.com/watch?v=6SA6S9Ca5-U From dd07f7f59fd56d132db619524f6c060389490aad Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 19 Jul 2023 16:55:10 +0200 Subject: [PATCH 047/259] Added experimental Shuffle AI app: https://github.com/Shuffle/shaffuru/issues/170 --- email/1.2.0/src/app.py | 11 ++- shuffle-ai/1.0.0/Dockerfile | 56 +++++++++++ shuffle-ai/1.0.0/api.yaml | 54 +++++++++++ shuffle-ai/1.0.0/docker-compose.yml | 20 ++++ shuffle-ai/1.0.0/requirements.txt | 3 + shuffle-ai/1.0.0/src/app.py | 138 ++++++++++++++++++++++++++++ 6 files changed, 278 insertions(+), 4 deletions(-) create mode 100644 shuffle-ai/1.0.0/Dockerfile create mode 100644 shuffle-ai/1.0.0/api.yaml create mode 100644 shuffle-ai/1.0.0/docker-compose.yml create mode 100644 shuffle-ai/1.0.0/requirements.txt create mode 100644 shuffle-ai/1.0.0/src/app.py diff --git a/email/1.2.0/src/app.py b/email/1.2.0/src/app.py index 3187c900..182d72d9 100644 --- a/email/1.2.0/src/app.py +++ b/email/1.2.0/src/app.py @@ -394,12 +394,15 @@ def parse_email_file(self, file_id, file_extension): print("File: %s" % file_path) if file_extension.lower() == 'eml': print('working with .eml file') - ep = eml_parser.EmlParser() + ep = eml_parser.EmlParser(include_attachment_data=True, include_raw_body=True) try: parsed_eml = ep.decode_email_bytes(file_path['data']) + if str(parsed_eml["header"]["date"]) == "1970-01-01 00:00:00+00:00": + return {"success":False,"reason":"Not an EML file, or EML doesn't have a timestamp (required)"} + return json.dumps(parsed_eml, default=json_serial) except Exception as e: - return {"Success":"False","Message":f"Exception occured: {e}"} + return {"success":False,"Message":f"Exception occured: {e}"} elif file_extension.lower() == 'msg': print('working with .msg file') try: @@ -423,9 +426,9 @@ def parse_email_file(self, file_id, file_extension): result['body_data'] = msg.body return result except Exception as e: - return {"Success":"False","Message":f"Exception occured: {e}"} + return {"success":False,"Message":f"Exception occured: {e}"} else: - return {"Success":"False","Message":f"No file handler for file extension {file_extension}"} + return {"success":False,"Message":f"No file handler for file extension {file_extension}"} def parse_email_headers(self, email_headers): try: diff --git a/shuffle-ai/1.0.0/Dockerfile b/shuffle-ai/1.0.0/Dockerfile new file mode 100644 index 00000000..36f0f4d0 --- /dev/null +++ b/shuffle-ai/1.0.0/Dockerfile @@ -0,0 +1,56 @@ +# Base our app image off of the WALKOFF App SDK image +FROM frikky/shuffle:app_sdk as base + +# We're going to stage away all of the bloat from the build tools so lets create a builder stage +FROM base as builder + +# Install all alpine build tools needed for our pip installs +RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev git + +# Install all of our pip packages in a single directory that we can copy to our base image later +RUN mkdir /install +WORKDIR /install + +# Switch back to our base image and copy in all of our built packages and source code +FROM base +COPY --from=builder /install /usr/local +COPY src /app +COPY requirements.txt /requirements.txt +RUN python3 -m pip install -r /requirements.txt + +# Install any binary dependencies needed in our final image +# RUN apk --no-cache add --update my_binary_dependency +RUN apk --no-cache add jq git curl + +ENV SHELL=/bin/bash + +### Install Tesseract +ENV CC /usr/bin/clang +ENV CXX /usr/bin/clang++ +ENV LANG=C.UTF-8 +ENV TESSDATA_PREFIX=/usr/local/share/tessdata + +# Dev tools +WORKDIR /tmp +RUN apk update +RUN apk upgrade +RUN apk add file openssl openssl-dev bash tini leptonica-dev openjpeg-dev tiff-dev libpng-dev zlib-dev libgcc mupdf-dev jbig2dec-dev +RUN apk add freetype-dev openblas-dev ffmpeg-dev linux-headers aspell-dev aspell-en # enchant-dev jasper-dev +RUN apk add --virtual .dev-deps git clang clang-dev g++ make automake autoconf libtool pkgconfig cmake ninja +RUN apk add --virtual .dev-testing-deps -X http://dl-3.alpinelinux.org/alpine/edge/testing autoconf-archive +RUN ln -s /usr/include/locale.h /usr/include/xlocale.h + +RUN apk add tesseract-ocr +RUN apk add poppler-utils + +# Install from main +RUN mkdir /usr/local/share/tessdata +RUN mkdir src +RUN cd src +RUN wget https://github.com/tesseract-ocr/tessdata_fast/raw/main/eng.traineddata -P /usr/local/share/tessdata +RUN git clone --depth 1 https://github.com/tesseract-ocr/tesseract.git +#RUN cd tesseract && ./autogen.sh && ./configure --build=x86_64-alpine-linux-musl --host=x86_64-alpine-linux-musl && make && make install && cd /tmp/src + +# Finally, lets run our app! +WORKDIR /app +CMD ["python", "app.py", "--log-level", "DEBUG"] diff --git a/shuffle-ai/1.0.0/api.yaml b/shuffle-ai/1.0.0/api.yaml new file mode 100644 index 00000000..40e0832c --- /dev/null +++ b/shuffle-ai/1.0.0/api.yaml @@ -0,0 +1,54 @@ +--- +app_version: 1.0.0 +name: Shuffle AI +description: An EXPERIMENTAL AI tool app for Shuffle +tags: + - Shuffle +categories: + - Shuffle +contact_info: + name: "@frikkylikeme" + url: https://shuffler.io + email: support@shuffler.io +actions: + - name: extract_text_from_pdf + description: Returns text from a pdf + parameters: + - name: file_id + description: The file to find text in + required: true + multiline: false + example: "file_" + schema: + type: string + returns: + schema: + type: string + - name: extract_text_from_image + description: Returns text from an image + parameters: + - name: file_id + description: The file to find text in + required: true + multiline: false + example: "file_" + schema: + type: string + returns: + schema: + type: string + - name: transcribe_audio + description: Returns text from audio + parameters: + - name: file_id + description: The file containing the audio + required: true + multiline: false + example: "file_" + schema: + type: string + returns: + schema: + type: string + +large_image: data:image/png;base64,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 diff --git a/shuffle-ai/1.0.0/docker-compose.yml b/shuffle-ai/1.0.0/docker-compose.yml new file mode 100644 index 00000000..40ee05f6 --- /dev/null +++ b/shuffle-ai/1.0.0/docker-compose.yml @@ -0,0 +1,20 @@ +version: '3.4' +services: + hello_world: + build: + context: . + dockerfile: Dockerfile +# image: walkoff_registry:5000/walkoff_app_HelloWorld-v1-0 + deploy: + mode: replicated + replicas: 10 + restart_policy: + condition: none + restart: "no" + secrets: + - secret1 +secrets: + secret1: + file: ./secret_data + labels: + foo: bar diff --git a/shuffle-ai/1.0.0/requirements.txt b/shuffle-ai/1.0.0/requirements.txt new file mode 100644 index 00000000..b1fb92b5 --- /dev/null +++ b/shuffle-ai/1.0.0/requirements.txt @@ -0,0 +1,3 @@ +pytesseract +pdf2image +pypdf2 diff --git a/shuffle-ai/1.0.0/src/app.py b/shuffle-ai/1.0.0/src/app.py new file mode 100644 index 00000000..655f84bd --- /dev/null +++ b/shuffle-ai/1.0.0/src/app.py @@ -0,0 +1,138 @@ +import pytesseract +from pdf2image import convert_from_path +import PyPDF2 +import json +import tempfile + +from walkoff_app_sdk.app_base import AppBase + +class Tools(AppBase): + __version__ = "1.0.0" + app_name = "Shuffle AI" + + def __init__(self, redis, logger, console_logger=None): + super().__init__(redis, logger, console_logger) + + + def extract_text_from_pdf(self, file_id): + def extract_pdf_text(pdf_path): + with open(pdf_path, 'rb') as file: + pdf_reader = PyPDF2.PdfReader(file) + text = '' + for page in pdf_reader.pages: + text += page.extract_text() + + return text + + def extract_text_from_images(images): + text = '' + for image in images: + extracted_text = pytesseract.image_to_string(image, lang='eng') + text += extracted_text + return text + + def extract_text_from_pdf_with_images(pdf_path): + images = convert_from_path(pdf_path) + return extract_text_from_images(images) + + def export_text_to_json(image_text, extracted_text): + data = { + "success": True, + 'image_text': image_text, + 'extracted_text': extracted_text, + } + + #with open(output_path, 'w+') as file: + # json.dump(data, file, indent=4) + + return data + + pdf_data = self.get_file(file_id) + defaultdata = { + "success": False, + "file_id": file_id, + "filename": pdf_data["filename"], + "reason": "Something failed in reading and parsing the pdf. See error logs for more info", + } + + # Make a tempfile for the file data from self.get_file + # Make a tempfile with tempfile library + with tempfile.NamedTemporaryFile() as temp: + # Write the file data to the tempfile + # Get the path to the tempfile + temp.write(pdf_data["data"]) + pdf_path = temp.name + + # Extract text from the PDF + extracted_text_from_pdf = extract_pdf_text(pdf_path) + + # Extract text from the PDF using images + extracted_text_from_images = extract_text_from_pdf_with_images(pdf_path) + + # Combine the extracted text + + # Export combined text to JSON + #output_path = pdf_path.split(".")[0] + ".json" + exported_text = export_text_to_json(extracted_text_from_images, extracted_text_from_pdf) + exported_text["file_id"] = file_id + exported_text["filename"] = pdf_data["filename"] + return exported_text + + return defaultdata + + def extract_text_from_image(self, file_id): + # Check if it's a pdf + # If it is, use extract_text_from_pdf + # If it's not, use pytesseract + if self.get_file(file_id)["name"].endswith(".pdf"): + return self.extract_text_from_pdf(file_id) + + pdf_data = self.get_file(file_id) + defaultdata = { + "success": False, + "file_id": file_id, + "filename": pdf_data["filename"], + "reason": "Something failed in reading and parsing the pdf. See error logs for more info", + } + + with tempfile.NamedTemporaryFile() as temp: + # Load temp as Image + # Write the file data to the tempfile + # Get the path to the tempfile + temp.write(pdf_data["data"]) + pdf_path = temp.name + + image = Image.open(temp.name) + image = image.resize((500,300)) + custom_config = r'-l eng --oem 3 --psm 6' + text = pytesseract.image_to_string(image,config=custom_config + + data = { + "success": True, + 'extracted_text': text, + } + + return data + + return defaultdata + + def transcribe_audio(self, file_id): + return { + "success": False, + "reason": "Not implemented yet" + } + + def find_image_objects(self, file_id): + return { + "success": False, + "reason": "Not implemented yet" + } + + def gpt(self, input_text): + return { + "success": False, + "reason": "Not implemented yet" + } + +if __name__ == "__main__": + Tools.run() From ee03da3aceabce8360bb04174ddc50d3e0156352 Mon Sep 17 00:00:00 2001 From: Frikky Date: Tue, 25 Jul 2023 20:30:37 +0200 Subject: [PATCH 048/259] Added an autoformatter to shuffle-ai app --- email/1.2.0/src/app.py | 2 +- shuffle-ai/1.0.0/api.yaml | 27 +++++++++++++++++++++++++++ shuffle-ai/1.0.0/src/app.py | 30 ++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/email/1.2.0/src/app.py b/email/1.2.0/src/app.py index 182d72d9..4a27c9b2 100644 --- a/email/1.2.0/src/app.py +++ b/email/1.2.0/src/app.py @@ -394,7 +394,7 @@ def parse_email_file(self, file_id, file_extension): print("File: %s" % file_path) if file_extension.lower() == 'eml': print('working with .eml file') - ep = eml_parser.EmlParser(include_attachment_data=True, include_raw_body=True) + ep = eml_parser.EmlParser(include_attachment_data=True, include_raw_body=True, parse_attachment=True) try: parsed_eml = ep.decode_email_bytes(file_path['data']) if str(parsed_eml["header"]["date"]) == "1970-01-01 00:00:00+00:00": diff --git a/shuffle-ai/1.0.0/api.yaml b/shuffle-ai/1.0.0/api.yaml index 40e0832c..31fbf7d5 100644 --- a/shuffle-ai/1.0.0/api.yaml +++ b/shuffle-ai/1.0.0/api.yaml @@ -11,6 +11,33 @@ contact_info: url: https://shuffler.io email: support@shuffler.io actions: + - name: autoformat_text + description: Input ANY kind of data in the format you want, and the format you want it in. Default is a business-y email. Uses ShuffleGPT, which is based on OpenAI and our own model. + parameters: + - name: apikey + description: Your https://shuffler.io apikey + required: true + multiline: false + example: "" + schema: + type: string + - name: text + description: The text you want to be converted (ANY format) + required: true + multiline: true + example: "Bad IPs are 1.2.3.4 and there's no good way to format this. JSON works too!" + schema: + type: string + - name: formatting + description: The format to use. + required: false + multiline: true + example: "Make it work as a ticket we can put in service now that is human readable for security analysts" + schema: + type: string + returns: + schema: + type: string - name: extract_text_from_pdf description: Returns text from a pdf parameters: diff --git a/shuffle-ai/1.0.0/src/app.py b/shuffle-ai/1.0.0/src/app.py index 655f84bd..49070446 100644 --- a/shuffle-ai/1.0.0/src/app.py +++ b/shuffle-ai/1.0.0/src/app.py @@ -13,6 +13,36 @@ class Tools(AppBase): def __init__(self, redis, logger, console_logger=None): super().__init__(redis, logger, console_logger) + def autoformat_text(self, apikey, text, formatting="auto"): + headers = { + "Authorization": "Bearer %s" % apikey, + } + + if not formatting: + formatting = "auto" + + output_formatting= "Format the following data to be a good email that can be sent to customers. Don't make it too business sounding." + if formatting != "auto": + output_formatting = formatting + + ret = requests.post( + "https://shuffler.io/api/v1/conversation", + json={ + "query": text, + "formatting": output_formatting, + "output_format": "formatting" + }, + headers=headers, + ) + + if ret.status_code != 200: + print(ret.text) + return { + "success": False, + "reason": "Status code for auto-formatter is not 200" + } + + return ret.text def extract_text_from_pdf(self, file_id): def extract_pdf_text(pdf_path): From 1f2e41bb837e6a1a0e55ec74a8f81f0b1a20040f Mon Sep 17 00:00:00 2001 From: Frikky Date: Sat, 5 Aug 2023 14:23:56 +0200 Subject: [PATCH 049/259] Added a new version of the email app with a msg/eml standard parser that handles attachments as files --- email/1.3.0/Dockerfile | 27 ++ email/1.3.0/api.yaml | 280 +++++++++++++++ email/1.3.0/requirements.txt | 8 + email/1.3.0/src/app.py | 615 +++++++++++++++++++++++++++++++++ email/1.3.0/src/sample.eml | 64 ++++ email/1.3.0/src/sample.msg | Bin 0 -> 9728 bytes shuffle-ai/1.0.0/src/app.py | 2 +- shuffle-tools/1.2.0/src/app.py | 2 +- 8 files changed, 996 insertions(+), 2 deletions(-) create mode 100644 email/1.3.0/Dockerfile create mode 100644 email/1.3.0/api.yaml create mode 100644 email/1.3.0/requirements.txt create mode 100644 email/1.3.0/src/app.py create mode 100644 email/1.3.0/src/sample.eml create mode 100644 email/1.3.0/src/sample.msg diff --git a/email/1.3.0/Dockerfile b/email/1.3.0/Dockerfile new file mode 100644 index 00000000..bcc1273d --- /dev/null +++ b/email/1.3.0/Dockerfile @@ -0,0 +1,27 @@ +# Base our app image off of the WALKOFF App SDK image +FROM frikky/shuffle:app_sdk as base + +# We're going to stage away all of the bloat from the build tools so lets create a builder stage +FROM base as builder + +# Install all alpine build tools needed for our pip installs +RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev + +# Install all of our pip packages in a single directory that we can copy to our base image later +RUN mkdir /install +WORKDIR /install +COPY requirements.txt /requirements.txt +RUN pip install --prefix="/install" -r /requirements.txt + +# Switch back to our base image and copy in all of our built packages and source code +FROM base +COPY --from=builder /install /usr/local +COPY src /app + +# Install any binary dependencies needed in our final image +# RUN apk --no-cache add --update my_binary_dependency + +# Finally, lets run our app! +WORKDIR /app +CMD python app.py --log-level DEBUG + diff --git a/email/1.3.0/api.yaml b/email/1.3.0/api.yaml new file mode 100644 index 00000000..aa29bc5b --- /dev/null +++ b/email/1.3.0/api.yaml @@ -0,0 +1,280 @@ +walkoff_version: 1.3.0 +app_version: 1.3.0 +name: email +description: Email app +tags: + - email +categories: + - communication +contact_info: + name: "@frikkylikeme" + url: https://github.com/frikky + email: "frikky@shuffler.io" +actions: + - name: send_email_shuffle + description: Send an email from Shuffle + parameters: + - name: apikey + description: Your https://shuffler.io apikey + multiline: false + example: "https://shuffler.io apikey" + required: true + schema: + type: string + - name: recipients + description: The recipients of the email + multiline: false + example: "test@example.com,frikky@shuffler.io" + required: true + schema: + type: string + - name: subject + description: The subject to use + multiline: false + example: "SOS this is an alert :o" + required: true + schema: + type: string + - name: body + description: The body to add to the email + multiline: true + example: "This is an email alert from Shuffler.io :)" + required: true + schema: + type: string + returns: + schema: + type: string + - name: send_email_smtp + description: Send an email with SMTP + parameters: + - name: username + description: The SMTP login username + multiline: false + example: "frikky@shuffler.io" + required: true + schema: + type: string + - name: password + description: The password to log in with SMTP + multiline: false + example: "******************" + required: true + schema: + type: string + - name: smtp_host + description: The host of the SMTP + multiline: false + example: "smtp-mail.outlook.com" + required: true + schema: + type: string + - name: smtp_port + description: The port to use for SMTP + multiline: false + example: "587" + required: true + schema: + type: string + - name: recipient + description: The receiver(s) of the email + multiline: false + example: "frikky@shuffler.io,frikky@shuffler.io" + required: true + schema: + type: string + - name: subject + description: The subject of the email + multiline: false + example: "This is a subject, hello there :)" + required: true + schema: + type: string + - name: body + description: The body to add to the email + multiline: true + example: "This is an email alert from Shuffler.io :)" + required: true + schema: + type: string + - name: attachments + description: Send files from shuffle as part of the email + multiline: false + example: "file_id1,file_id2,file_id3" + required: false + schema: + type: string + - name: ssl_verify + description: Whether to use TLS or not + example: "true" + required: false + options: + - true + - false + schema: + type: string + - name: body_type + description: The type of body to send. HTML by default + example: "true" + required: false + options: + - "html" + - "plain" + schema: + type: string + returns: + schema: + type: string + - name: get_emails_imap + description: Get emails using IMAP (e.g. imap.gmail.com / Outlook.office365.com) + parameters: + - name: username + description: The SMTP login username + multiline: false + example: "frikky@shuffler.io" + required: true + schema: + type: string + - name: password + description: The password to log in with SMTP + multiline: false + example: "******************" + required: true + schema: + type: string + - name: imap_server + description: The imap server host + multiline: false + example: "Outlook.office365.com" + required: true + schema: + type: string + - name: foldername + description: The folder to use, e.g. "inbox" + multiline: false + example: "inbox" + required: true + schema: + type: string + - name: amount + description: Amount of emails to retrieve + multiline: false + example: "10" + required: true + schema: + type: string + - name: unread + description: Retrieve just unread emails + multiline: false + options: + - "false" + - "true" + required: true + schema: + type: bool + - name: fields + description: Comma separated list of fields to be exported + multiline: false + example: "body, header.subject, header.header.message-id" + required: false + schema: + type: string + - name: include_raw_body + description: Include raw body in email export + multiline: false + options: + - "true" + - "false" + required: true + schema: + type: bool + - name: include_attachment_data + description: Include raw attachments in email export + multiline: false + options: + - "false" + - "true" + required: true + schema: + type: bool + - name: upload_email_shuffle + description: Upload email in shuffle, return uid + multiline: false + options: + - "false" + - "true" + required: true + schema: + type: bool + - name: upload_attachments_shuffle + description: Upload attachments in shuffle, return uids + multiline: false + options: + - "false" + - "true" + required: true + schema: + type: bool + - name: ssl_verify + description: Whether to use TLS or not + example: "true" + required: false + options: + - true + - false + schema: + type: string + - name: mark_as_read + description: Mark email as read or not + multiline: false + options: + - "false" + - "true" + required: false + schema: + type: bool + - name: parse_email_file + description: Takes a file from shuffle and analyzes it if it's a valid .eml or .msg + parameters: + - name: file_id + description: file id + required: true + multiline: true + example: 'adf5e3d0fd85633be17004735a0a119e' + schema: + type: string + - name: extract_attachments + description: Whether to extract the attachments straight into files + required: true + options: + - true + - false + example: 'true' + schema: + type: string + - name: parse_email_headers + description: + parameters: + - name: email_headers + description: Email headers + required: true + multiline: true + example: 'Email Headers' + schema: + type: string + returns: + schema: + type: string + - name: analyze_headers + description: + parameters: + - name: headers + description: Email headers in any format + required: true + multiline: true + schema: + type: string + returns: + schema: + type: string +large_image: data:image/png;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/hAytodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6Nzg4QTJBMjVEMDI1MTFFN0EwQUVDODc5QjYyQkFCMUQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6Nzg4QTJBMjZEMDI1MTFFN0EwQUVDODc5QjYyQkFCMUQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3ODhBMkEyM0QwMjUxMUU3QTBBRUM4NzlCNjJCQUIxRCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3ODhBMkEyNEQwMjUxMUU3QTBBRUM4NzlCNjJCQUIxRCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pv/bAEMAAwICAgICAwICAgMDAwMEBgQEBAQECAYGBQYJCAoKCQgJCQoM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 diff --git a/email/1.3.0/requirements.txt b/email/1.3.0/requirements.txt new file mode 100644 index 00000000..926027e8 --- /dev/null +++ b/email/1.3.0/requirements.txt @@ -0,0 +1,8 @@ +requests==2.25.1 +glom==20.11.0 +eml-parser==1.17.0 +msg-parser==1.2.0 +mail-parser==3.15.0 +extract-msg==0.30.9 +jsonpickle==2.0.0 + diff --git a/email/1.3.0/src/app.py b/email/1.3.0/src/app.py new file mode 100644 index 00000000..28ada785 --- /dev/null +++ b/email/1.3.0/src/app.py @@ -0,0 +1,615 @@ +import json +import uuid +import socket +import asyncio +import requests +import tempfile +import datetime +import base64 +import imaplib +import smtplib +import time +import random +import eml_parser +import mailparser +import extract_msg +import jsonpickle + +from glom import glom +from msg_parser import MsOxMessage +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText +from email.mime.application import MIMEApplication + +from walkoff_app_sdk.app_base import AppBase + +def json_serial(obj): + if isinstance(obj, datetime.datetime): + serial = obj.isoformat() + return serial + +def default(o): + """helpers to store item in json + arguments: + - o: field of the object to serialize + returns: + - valid serialized value for unserializable fields + """ + if isinstance(o, (datetime.date, datetime.datetime)): + return o.isoformat() + if isinstance(o, set): + return list(o) + if isinstance(o, bytes): + try: + return o.decode("utf-8") + except: + print("Failed parsing utf-8 string") + return o + + +class Email(AppBase): + __version__ = "1.3.0" + app_name = "email" + + def __init__(self, redis, logger, console_logger=None): + """ + Each app should have this __init__ to set up Redis and logging. + :param redis: + :param logger: + :param console_logger: + """ + super().__init__(redis, logger, console_logger) + + # This is an email function of Shuffle + def send_email_shuffle(self, apikey, recipients, subject, body): + targets = [recipients] + if ", " in recipients: + targets = recipients.split(", ") + elif "," in recipients: + targets = recipients.split(",") + + data = {"targets": targets, "body": body, "subject": subject, "type": "alert"} + + url = "https://shuffler.io/functions/sendmail" + headers = {"Authorization": "Bearer %s" % apikey} + return requests.post(url, headers=headers, json=data).text + + def send_email_smtp( + self, username, password, smtp_host, recipient, subject, body, smtp_port, attachments="", ssl_verify="True", body_type="html" + ): + if type(smtp_port) == str: + try: + smtp_port = int(smtp_port) + except ValueError: + return "SMTP port needs to be a number (Current: %s)" % smtp_port + + try: + s = smtplib.SMTP(host=smtp_host, port=smtp_port) + except socket.gaierror as e: + return f"Bad SMTP host or port: {e}" + + # This is not how it should work.. + # Port 465 & 587 = TLS. Sometimes 25. + if ssl_verify == "false" or ssl_verify == "False": + pass + else: + s.starttls() + + if len(username) > 0 or len(password) > 0: + try: + s.login(username, password) + except smtplib.SMTPAuthenticationError as e: + return { + "success": False, + "reason": f"Bad username or password: {e}" + } + + if body_type == "" or len(body_type) < 3: + body_type = "html" + + # setup the parameters of the message + msg = MIMEMultipart() + msg["From"] = username + msg["To"] = recipient + msg["Subject"] = subject + msg.attach(MIMEText(body, body_type)) + + # Read the attachments + attachment_count = 0 + try: + if attachments != None and len(attachments) > 0: + print("Got attachments: %s" % attachments) + attachmentsplit = attachments.split(",") + + #attachments = parse_list(attachments, splitter=",") + #print("Got attachments2: %s" % attachmentsplit) + print("Before loop") + files = [] + for file_id in attachmentsplit: + print(f"Looping {file_id}") + file_id = file_id.strip() + new_file = self.get_file(file_id) + print(f"New file: {new_file}") + try: + part = MIMEApplication( + new_file["data"], + Name=new_file["filename"], + ) + part["Content-Disposition"] = f"attachment; filename=\"{new_file['filename']}\"" + msg.attach(part) + attachment_count += 1 + except Exception as e: + print(f"[WARNING] Failed to attach {file_id}: {e}") + + + #files.append(new_file) + + #return files + #data["attachments"] = files + except Exception as e: + self.logger.info(f"Error in attachment parsing for email: {e}") + + + try: + s.send_message(msg) + except smtplib.SMTPDataError as e: + return { + "success": False, + "reason": f"Failed to send mail: {e}" + } + + self.logger.info("Successfully sent email with subject %s to %s" % (subject, recipient)) + return { + "success": True, + "reason": "Email sent to %s!" % recipient, + "attachments": attachment_count + } + + def get_emails_imap( + self, + username, + password, + imap_server, + foldername, + amount, + unread, + fields, + include_raw_body, + include_attachment_data, + upload_email_shuffle, + upload_attachments_shuffle, + ssl_verify="True", + mark_as_read="False", + ): + def path_to_dict(path, value=None): + def pack(parts): + return ( + {parts[0]: pack(parts[1:]) if len(parts) > 1 else value} + if len(parts) > 1 + else {parts[0]: value} + ) + + return pack(path.split(".")) + + def merge(d1, d2): + for k in d2: + if k in d1 and isinstance(d1[k], dict) and isinstance(d2[k], dict): + merge(d1[k], d2[k]) + else: + d1[k] = d2[k] + + #if isinstance(mark_as_read, str): + # if str(mark_as_read).lower() == "true": + # mark_as_read = True + # else: + # mark_as_read = False + + if type(amount) == str: + try: + amount = int(amount) + except ValueError: + return { + "success": False, + "reason": "Amount needs to be a number, not %s" % amount, + } + + try: + email = imaplib.IMAP4_SSL(imap_server) + except ConnectionRefusedError as error: + try: + email = imaplib.IMAP4(imap_server) + + if ssl_verify == "false" or ssl_verify == "False" or ssl_verify == False: + pass + else: + email.starttls() + except socket.gaierror as error: + return { + "success": False, + "reason": "Can't connect to IMAP server %s: %s" % (imap_server, error), + } + except socket.gaierror as error: + return { + "success": False, + "reason": "Can't connect to IMAP server %s: %s" % (imap_server, error), + } + + try: + email.login(username, password) + except imaplib.IMAP4.error as error: + return { + "success": False, + "reason": "Failed to log into %s: %s" % (username, error), + } + + email.select(foldername) + unread = True if unread.lower().strip() == "true" else False + + try: + # IMAP search queries, e.g. "seen" or "read" + # https://www.rebex.net/secure-mail.net/features/imap-search.aspx + mode = "(UNSEEN)" if unread else "ALL" + thistype, data = email.search(None, mode) + except imaplib.IMAP4.error as error: + return { + "success": False, + "reason": "Couldn't find folder %s." % (foldername), + } + + email_ids = data[0] + id_list = email_ids.split() + if id_list == None: + return { + "success": False, + "reason": f"Couldn't retrieve email. Data: {data}", + } + + #try: + # self.logger.info(f"LIST: {id_list}") + #except TypeError: + # return { + # "success": False, + # "reason": "Error getting email. Data: %s" % data, + # } + + mark_as_read = True if str(mark_as_read).lower().strip() == "true" else False + include_raw_body = True if str(include_raw_body).lower().strip() == "true" else False + include_attachment_data = ( + True if str(include_attachment_data).lower().strip() == "true" else False + ) + upload_email_shuffle = ( + True if str(upload_email_shuffle).lower().strip() == "true" else False + ) + upload_attachments_shuffle = ( + True if str(upload_attachments_shuffle).lower().strip() == "true" else False + ) + + # Convert of mails in json + emails = [] + ep = eml_parser.EmlParser( + include_attachment_data=include_attachment_data + or upload_attachments_shuffle, + include_raw_body=include_raw_body, + ) + + if len(id_list) == 0: + return { + "success": True, + "messages": [], + } + + try: + amount = len(id_list) if len(id_list) eml) + # Write to file + emldata = msg.get_email_mime_content() + file_path["data"] = emldata.encode() + + #frozen = jsonpickle.encode(msg_properties_dict, unpicklable = False) + #json_response = json.loads(frozen) + #if json_response.get("attachments"): + # for i in json_response["attachments"]: + # if isinstance(i,dict): + # if i.get('data') and 'text' in i.get('AttachMimeTag'): + # value = i.get('data').get('py/b64') + # i['data']['content'] = base64.b64decode(value).decode() + + #result['attachments'] = json_response['attachments'] + + #ep = eml_parser.EmlParser() + + #temp = ep.decode_email_bytes(bytes(msg.get_email_mime_content(),'utf-8')) + #result['body'] = temp['body'] + #msg1 = extract_msg.openMsg(file_path['data']) + #result["header"] = dict(msg1.header.items()) + #result['body_data'] = msg.body + + except Exception as e: + return {"success":False,"reason":f"Exception occured during msg parsing: {e}"} + + ep = eml_parser.EmlParser( + include_attachment_data=True, + include_raw_body=True + ) + + try: + print("Pre email") + parsed_eml = ep.decode_email_bytes(file_path['data']) + if str(parsed_eml["header"]["date"]) == "1970-01-01 00:00:00+00:00" and len(parsed_eml["header"]["subject"]) == 0: + return {"success":False,"reason":"Not a valid EML/MSG file, or the file have a timestamp or subject defined (required)."} + + # Put attachments in the shuffle file system + print("Pre attachment") + if extract_attachments == True and "attachment" in parsed_eml: + cnt = -1 + + print("[INFO] Uploading %d attachments" % len(parsed_eml["attachment"])) + for value in parsed_eml["attachment"]: + cnt += 1 + if value["raw"] == None: + parsed_eml["attachment"][cnt]["file_id"] = "Not available" + continue + + file = { + "filename": value["filename"], + "data": base64.b64decode(value["raw"]), + } + + file_id = self.set_files([file]) + if len(file_id) == 0: + parsed_eml["attachment"][cnt]["file_id"] = "File upload failed" + else: + parsed_eml["attachment"][cnt]["file_id"] = file_id[0] + + if not "attachment" in parsed_eml: + parsed_eml["attachment"] = [] + + print("Post attachment") + return json.dumps(parsed_eml, default=json_serial) + except Exception as e: + return {"success":False,"reason":f"An exception occured during EML parsing: {e}. Please contact support"} + + return {"success": False, "reason": "No email has been defined for this file type"} + + + def parse_email_headers(self, email_headers): + try: + email_headers = bytes(email_headers,'utf-8') + ep = eml_parser.EmlParser() + parsed_headers = ep.decode_email_bytes(email_headers) + return json.dumps(parsed_headers, default=json_serial) + except Exception as e: + raise Exception(e) + + # Basic function to check headers in an email + # Can be dumped in in pretty much any format + def analyze_headers(self, headers): + # Raw + if isinstance(headers, str): + headers = self.parse_email_headers(headers) + if isinstance(headers, str): + headers = json.loads(headers) + + headers = headers["header"]["header"] + + # Just a way to parse out shitty email formats + if "header" in headers: + headers = headers["header"] + if "header" in headers: + headers = headers["header"] + + if not isinstance(headers, list): + newheaders = [] + for key, value in headers.items(): + if isinstance(value, list): + newheaders.append({ + "key": key, + "value": value[0], + }) + else: + newheaders.append({ + "key": key, + "value": value, + }) + + headers = newheaders + + + spf = False + dkim = False + dmarc = False + spoofed = False + + analyzed_headers = { + "success": True, + } + + for item in headers: + if "name" in item: + item["key"] = item["name"] + + item["key"] = item["key"].lower() + + if "spf" in item["key"]: + if "pass " in item["value"].lower(): + spf = True + + if "dkim" in item["key"]: + if "pass " in item["value"].lower(): + dkim = True + + if "dmarc" in item["key"]: + print("dmarc: ", item["key"]) + + if item["key"].lower() == "authentication-results": + if "spf=pass" in item["value"]: + spf = True + if "dkim=pass" in item["value"]: + dkim = True + if "dmarc=pass" in item["value"]: + dmarc = True + + # Fix spoofed! + if item["key"] == "from": + print("From: " + item["value"]) + + if "<" in item["value"]: + item["value"] = item["value"].split("<")[1] + + for subitem in headers: + if "name" in subitem: + subitem["key"] = subitem["name"] + + subitem["key"] = subitem["key"].lower() + + if subitem["key"] == "reply-to": + + if "<" in subitem["value"]: + subitem["value"] = subitem["value"].split("<")[1] + + if item["value"] != subitem["value"]: + spoofed = True + analyzed_headers["spoofed_reason"] = "Reply-To is different than From" + break + + if subitem["key"] == "mail-reply-to": + print("Reply-To: " + subitem["value"], item["value"]) + + if "<" in subitem["value"]: + subitem["value"] = subitem["value"].split("<")[1] + + if item["value"] != subitem["value"]: + spoofed = True + analyzed_headers["spoofed_reason"] = "Mail-Reply-To is different than From" + break + + analyzed_headers["spf"] = spf + analyzed_headers["dkim"] = dkim + analyzed_headers["dmarc"] = dmarc + analyzed_headers["spoofed"] = spoofed + + # Should be a dictionary + return analyzed_headers + + +# Run the actual thing after we've checked params +def run(request): + action = request.get_json() + authorization_key = action.get("authorization") + current_execution_id = action.get("execution_id") + + if action and "name" in action and "app_name" in action: + Email.run(action) + return f'Attempting to execute function {action["name"]} in app {action["app_name"]}' + else: + return f"Invalid action" + + +if __name__ == "__main__": + Email.run() diff --git a/email/1.3.0/src/sample.eml b/email/1.3.0/src/sample.eml new file mode 100644 index 00000000..c608a54a --- /dev/null +++ b/email/1.3.0/src/sample.eml @@ -0,0 +1,64 @@ +FCC: imap://piro-test@mail.clear-code.com/Sent +X-Identity-Key: id1 +X-Account-Key: account1 +From: "piro-test@clear-code.com" +Subject: test confirmation +To: piro.outsider.reflex+1@gmail.com, piro.outsider.reflex+2@gmail.com, + mailmaster@example.com, mailmaster@example.org, webmaster@example.com, + webmaster@example.org, webmaster@example.jp, mailmaster@example.jp +Message-ID: <05c18622-f2ad-cb77-2ce9-a0bbfc7d7ad0@clear-code.com> +Date: Thu, 15 Aug 2019 14:54:37 +0900 +X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; DSN=0; uuencode=0; + attachmentreminder=0; deliveryformat=4 +User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:69.0) Gecko/20100101 + Thunderbird/69.0 +MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary="------------26A45336F6C6196BD8BBA2A2" +Content-Language: en-US + +This is a multi-part message in MIME format. +--------------26A45336F6C6196BD8BBA2A2 +Content-Type: text/plain; charset=utf-8; format=flowed +Content-Transfer-Encoding: 7bit + +testtest +testtest +testtest +testtest +testtest +testtest + + + +--------------26A45336F6C6196BD8BBA2A2 +Content-Type: text/plain; charset=UTF-8; + name="sha1hash.txt" +Content-Transfer-Encoding: base64 +Content-Disposition: attachment; + filename="sha1hash.txt" + +NzRjOGYwOWRmYTMwZWFjY2ZiMzkyYjEzMjMxNGZjNmI5NzhmMzI1YSAqZmxleC1jb25maXJt +LW1haWwuMS4xMC4wLnhwaQpjY2VlNGI0YWE0N2Y1MTNhYmNlMzQyY2UxZTJlYzJmZDk2MDBl +MzFiICpmbGV4LWNvbmZpcm0tbWFpbC4xLjExLjAueHBpCjA3MWU5ZTM3OGFkMDE3OWJmYWRi +MWJkYzY1MGE0OTQ1NGQyMDRhODMgKmZsZXgtY29uZmlybS1tYWlsLjEuMTIuMC54cGkKOWQ3 +YWExNTM0MThlYThmYmM4YmU3YmE2ZjU0Y2U4YTFjYjdlZTQ2OCAqZmxleC1jb25maXJtLW1h +aWwuMS45LjkueHBpCjgxNjg1NjNjYjI3NmVhNGY5YTJiNjMwYjlhMjA3ZDkwZmIxMTg1NmUg +KmZsZXgtY29uZmlybS1tYWlsLnhwaQo= +--------------26A45336F6C6196BD8BBA2A2 +Content-Type: application/json; + name="manifest.json" +Content-Transfer-Encoding: base64 +Content-Disposition: attachment; + filename="manifest.json" + +ewogICJtYW5pZmVzdF92ZXJzaW9uIjogMiwKICAiYXBwbGljYXRpb25zIjogewogICAgImdl +Y2tvIjogewogICAgICAiaWQiOiAiZmxleGlibGUtY29uZmlybS1tYWlsQGNsZWFyLWNvZGUu +Y29tIiwKICAgICAgInN0cmljdF9taW5fdmVyc2lvbiI6ICI2OC4wIgogICAgfQogIH0sCiAg +Im5hbWUiOiAiRmxleCBDb25maXJtIE1haWwiLAogICJkZXNjcmlwdGlvbiI6ICJDb25maXJt +IG1haWxhZGRyZXNzIGFuZCBhdHRhY2htZW50cyBiYXNlZCBvbiBmbGV4aWJsZSBydWxlcy4i +LAogICJ2ZXJzaW9uIjogIjIuMCIsCgogICJsZWdhY3kiOiB7CiAgICAidHlwZSI6ICJ4dWwi +LAogICAgIm9wdGlvbnMiOiB7CiAgICAgICJwYWdlIjogImNocm9tZTovL2NvbmZpcm0tbWFp +bC9jb250ZW50L3NldHRpbmcueHVsIiwKICAgICAgIm9wZW5faW5fdGFiIjogdHJ1ZQogICAg +fQogIH0KfQ== +--------------26A45336F6C6196BD8BBA2A2-- diff --git a/email/1.3.0/src/sample.msg b/email/1.3.0/src/sample.msg new file mode 100644 index 0000000000000000000000000000000000000000..ded03fdf23a72dbfbf3af272a4a0308473b0b0fd GIT binary patch literal 9728 zcmeHM&2Jk;6n|^Cq)qC60BvbX-AzBBq{P_HSCjCOv~^Gs~7LMdRI>dGmhr z-n{o_X7lSW`~JN7WA|UwKs^+uM{7Ijg-z`mzjt``y`=ik_GoQw4bA(w@auDWhAgl{ zcUSlp7LaJteaL@z-*EjvKBAtp-SJZw(y?jFG3%wuYq_eKaf;=6JyX`LC8uOr3;LpI z+nIS&FBD6GaxbN4FN{uJoJo(LpGi+lO~=m7q~+*Dg{rkUp0^e=#d0iXEw(aL@wPj@ zUH!G&ckcJz-Swd7URu99e&f5}ZXNt-`ct_YANPG&w#~9LswX0yY1i<5Q|CwZw{wGM zvWe_qvM`h=3=E$c$z}%P{rw}kA#-G;e_$Z~ZlrUPjW?<{m>|-5X({`enPZVb%0)V7 ztx?^vHZ1^8Vk({L`^c=?MXSuSip3+HkxpNQzBqmKyPv<^d+3`o{jTkq4H0SA3sfbG z7HORF>Y1S;l_^F!RU^7YX_}=AEH{YL7rJf#Y1e---hYPvy$y@!&%8x%-QsQ0AJi1& zB0=M4FBk3ggu$;$o|)IP%ld>}v1~Jz;@B=V6%*qP6eXSdU;<1lM%5`Kj7-@s8ii~z zU$oVGt}>rU4knGfSt!Ku>twRS$+!XYIp*iiiczr2j*~6%3%A{)CK5bspFLJ6WkF7FWqLD4hk+Zy*wMt{FtC7wXBWIPYDvKO5s%Abhk~HS4rdh^I zwzOpObBx)Ht3~tLLb045RBv`JQ!*2Aj+qss*T^m`F4#_{>gZ?Fb7xcUPff2Hl}t5n zTqzlq5~n<#0+fia2+fFz)tfRXOVo2eUi&)r==kHUt+(JD1oED4jX3^0;@Dv?-@Mm* z|ITe~oOaaJ!}MT}cg6}cUzq+?bxtDuj`GKQ=>PknS3kIZ?m~YT9o}6(J~Dv%@@|(u zO5KWor;`a{|wRnM;TcdI%dKNVh-j@H#f+L)5(jgQh< zRo~z@8|q;-Kdc+<8KqD|eO2-QQ0otAJC*)P9uOwXe^Avu`-Evo@iV)U7t*@adb{}G z&#Qh9kJ-KgZ#%Tr{^RgI6TJ8Mami-XZvJg||9Besqq8$lczWBgQAqnw#qBn3f?tHR zHC6wlR*#aV{J@J7_A^!Ah{JGQzvnkE)y9Wuzw&d>Z`fBA|HcfS>J9OBa5z#rxMQ7$?c#LY1-$GM!~a*~TD8#%^%-~5bm`&+d>emti(EO3$Y zghdXoMT*a-4xd_=TatEwwrk#X7rxmQ!Olf=Oa1!XC*e#?bNpAP44;%uJ|XA1c9q|% zWOL2po&=wn<1Xu#zo$r3_}Ft?nBbF7o4?e7(XuVHV5QhcfYLo_JHkF!T z)8G5{?@IOmg|B35EdKcN{QDxylj@JP3idzf5BtmX$2nb!kF^TUf51=vpA_RmAF2LWt6=*>cbvau@sD_t;>+?M@Nxc<;p4vZ^zr5SA9pP&K71812x`9m zsha=G^v69u(V`Qyv;-(i*|#fPur`w_?wzrTh3 z_f?i9#m8KAE}&-kuIJ0oe@9rB6d!ZleE+Ga|CLnKz0cu$pUHPI={+!ByMN)Ef)pQf z9j{#$p?{STbBtSQf;z`vCp=&Do-9JqXzl(9Y<$ybm73W<&R6G2{+1}+e{=?x>i-(^ z2Iqg^Bmc|xzn&KG5qtP925P3imw91tN&FvUSyKJqVBVno=ib=|ECfEusz6?>i^rv-Cz2%w9>o&{Y#49if{pM wTlm<9kP7il0zQemInGBYxF_MRg8PE^{TFeAyB5xG$l>672*sb%GiibU0e5D^J^%m! literal 0 HcmV?d00001 diff --git a/shuffle-ai/1.0.0/src/app.py b/shuffle-ai/1.0.0/src/app.py index 49070446..9724a02f 100644 --- a/shuffle-ai/1.0.0/src/app.py +++ b/shuffle-ai/1.0.0/src/app.py @@ -135,7 +135,7 @@ def extract_text_from_image(self, file_id): image = Image.open(temp.name) image = image.resize((500,300)) custom_config = r'-l eng --oem 3 --psm 6' - text = pytesseract.image_to_string(image,config=custom_config + text = pytesseract.image_to_string(image,config=custom_config) data = { "success": True, diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 13cb7b4b..c8fdb99d 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -1456,7 +1456,7 @@ def merge_lists(self, list_one, list_two, set_field="", sort_key_list_one="", so return list_one def merge_json_objects(self, list_one, list_two, set_field="", sort_key_list_one="", sort_key_list_two=""): - self.merge_lists(self, list_one, list_two, set_field="", sort_key_list_one="", sort_key_list_two="") + return self.merge_lists(list_one, list_two, set_field=set_field, sort_key_list_one=sort_key_list_one, sort_key_list_two=sort_key_list_two) def fix_json(self, json_data): try: From 5b8234382405ba363c2ab226f59520209ca9b021 Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 16 Aug 2023 02:22:11 +0200 Subject: [PATCH 050/259] Minor change to required fields for shuffle tools --- shuffle-tools/1.2.0/api.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shuffle-tools/1.2.0/api.yaml b/shuffle-tools/1.2.0/api.yaml index 50f19a25..66dff115 100644 --- a/shuffle-tools/1.2.0/api.yaml +++ b/shuffle-tools/1.2.0/api.yaml @@ -166,7 +166,7 @@ actions: type: string - name: field description: The field to check - required: false + required: true multiline: false example: "data" schema: @@ -192,7 +192,7 @@ actions: type: string - name: value description: The value to check with - required: false + required: true multiline: false example: "1.2.3.4" schema: From d91fc5cee0ab390145e169432b3ef0395c03ff73 Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 24 Aug 2023 13:22:15 +0200 Subject: [PATCH 051/259] Fixed Mailbox for Exchange. Force update needed --- email/1.3.0/src/app.py | 54 +++++++++++++------------ email/1.3.0/src/sample.eml | 64 ------------------------------ email/1.3.0/src/sample.msg | Bin 9728 -> 0 bytes outlook-exchange/1.0.0/src/app.py | 11 +++-- 4 files changed, 33 insertions(+), 96 deletions(-) delete mode 100644 email/1.3.0/src/sample.eml delete mode 100644 email/1.3.0/src/sample.msg diff --git a/email/1.3.0/src/app.py b/email/1.3.0/src/app.py index 28ada785..b52bd92f 100644 --- a/email/1.3.0/src/app.py +++ b/email/1.3.0/src/app.py @@ -400,38 +400,19 @@ def parse_email_file(self, file_id, extract_attachments=False): else: extract_attachments = False - if ".msg" in file_path["filename"]: - print('working with .msg file') + # Makes msg into eml + if ".msg" in file_path["filename"] or "." not in file_path["filename"]: + print(f"[DEBUG] Working with .msg file {file_path['filename']}. Filesize: {len(file_path['data'])}") try: result = {} msg = MsOxMessage(file_path['data']) - # Create tempfile to overwrite the data with the extracted email (msg -> eml) - # Write to file emldata = msg.get_email_mime_content() file_path["data"] = emldata.encode() - #frozen = jsonpickle.encode(msg_properties_dict, unpicklable = False) - #json_response = json.loads(frozen) - #if json_response.get("attachments"): - # for i in json_response["attachments"]: - # if isinstance(i,dict): - # if i.get('data') and 'text' in i.get('AttachMimeTag'): - # value = i.get('data').get('py/b64') - # i['data']['content'] = base64.b64decode(value).decode() - - #result['attachments'] = json_response['attachments'] - - #ep = eml_parser.EmlParser() - - #temp = ep.decode_email_bytes(bytes(msg.get_email_mime_content(),'utf-8')) - #result['body'] = temp['body'] - #msg1 = extract_msg.openMsg(file_path['data']) - #result["header"] = dict(msg1.header.items()) - #result['body_data'] = msg.body - except Exception as e: - return {"success":False,"reason":f"Exception occured during msg parsing: {e}"} + if ".msg" in file_path["filename"]: + return {"success":False, "reason":f"Exception occured during msg parsing: {e}"} ep = eml_parser.EmlParser( include_attachment_data=True, @@ -442,7 +423,7 @@ def parse_email_file(self, file_id, extract_attachments=False): print("Pre email") parsed_eml = ep.decode_email_bytes(file_path['data']) if str(parsed_eml["header"]["date"]) == "1970-01-01 00:00:00+00:00" and len(parsed_eml["header"]["subject"]) == 0: - return {"success":False,"reason":"Not a valid EML/MSG file, or the file have a timestamp or subject defined (required)."} + return {"success":False,"reason":"Not a valid EML/MSG file, or the file have a timestamp or subject defined (required).", "date": str(parsed_eml["header"]["date"]), "subject": str(parsed_eml["header"]["subject"])} # Put attachments in the shuffle file system print("Pre attachment") @@ -473,7 +454,7 @@ def parse_email_file(self, file_id, extract_attachments=False): print("Post attachment") return json.dumps(parsed_eml, default=json_serial) except Exception as e: - return {"success":False,"reason":f"An exception occured during EML parsing: {e}. Please contact support"} + return {"success":False, "reason": f"An exception occured during EML parsing: {e}. Please contact support"} return {"success": False, "reason": "No email has been defined for this file type"} @@ -528,6 +509,12 @@ def analyze_headers(self, headers): analyzed_headers = { "success": True, + "details": { + "spf": "", + "dkim": "", + "dmarc": "", + "spoofed": "", + } } for item in headers: @@ -537,17 +524,29 @@ def analyze_headers(self, headers): item["key"] = item["key"].lower() if "spf" in item["key"]: + analyzed_headers["details"]["spf"] = spf if "pass " in item["value"].lower(): spf = True if "dkim" in item["key"]: + analyzed_headers["details"]["dkim"] = dkim if "pass " in item["value"].lower(): dkim = True if "dmarc" in item["key"]: + analyzed_headers["details"]["dmarc"] = dmarc print("dmarc: ", item["key"]) if item["key"].lower() == "authentication-results": + if "spf" in item["value"].lower(): + analyzed_headers["details"]["spf"] = spf + + if "dkim" in item["value"].lower(): + analyzed_headers["details"]["dkim"] = dkim + + if "dmarc" in item["value"].lower(): + analyzed_headers["details"]["dmarc"] = dmarc + if "spf=pass" in item["value"]: spf = True if "dkim=pass" in item["value"]: @@ -576,8 +575,10 @@ def analyze_headers(self, headers): if item["value"] != subitem["value"]: spoofed = True analyzed_headers["spoofed_reason"] = "Reply-To is different than From" + analyzed_headers["details"]["spoofed"] = subitem["value"] break + if subitem["key"] == "mail-reply-to": print("Reply-To: " + subitem["value"], item["value"]) @@ -587,6 +588,7 @@ def analyze_headers(self, headers): if item["value"] != subitem["value"]: spoofed = True analyzed_headers["spoofed_reason"] = "Mail-Reply-To is different than From" + analyzed_headers["details"]["spoofed"] = subitem["value"] break analyzed_headers["spf"] = spf diff --git a/email/1.3.0/src/sample.eml b/email/1.3.0/src/sample.eml deleted file mode 100644 index c608a54a..00000000 --- a/email/1.3.0/src/sample.eml +++ /dev/null @@ -1,64 +0,0 @@ -FCC: imap://piro-test@mail.clear-code.com/Sent -X-Identity-Key: id1 -X-Account-Key: account1 -From: "piro-test@clear-code.com" -Subject: test confirmation -To: piro.outsider.reflex+1@gmail.com, piro.outsider.reflex+2@gmail.com, - mailmaster@example.com, mailmaster@example.org, webmaster@example.com, - webmaster@example.org, webmaster@example.jp, mailmaster@example.jp -Message-ID: <05c18622-f2ad-cb77-2ce9-a0bbfc7d7ad0@clear-code.com> -Date: Thu, 15 Aug 2019 14:54:37 +0900 -X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; DSN=0; uuencode=0; - attachmentreminder=0; deliveryformat=4 -User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:69.0) Gecko/20100101 - Thunderbird/69.0 -MIME-Version: 1.0 -Content-Type: multipart/mixed; - boundary="------------26A45336F6C6196BD8BBA2A2" -Content-Language: en-US - -This is a multi-part message in MIME format. ---------------26A45336F6C6196BD8BBA2A2 -Content-Type: text/plain; charset=utf-8; format=flowed -Content-Transfer-Encoding: 7bit - -testtest -testtest -testtest -testtest -testtest -testtest - - - ---------------26A45336F6C6196BD8BBA2A2 -Content-Type: text/plain; charset=UTF-8; - name="sha1hash.txt" -Content-Transfer-Encoding: base64 -Content-Disposition: attachment; - filename="sha1hash.txt" - -NzRjOGYwOWRmYTMwZWFjY2ZiMzkyYjEzMjMxNGZjNmI5NzhmMzI1YSAqZmxleC1jb25maXJt -LW1haWwuMS4xMC4wLnhwaQpjY2VlNGI0YWE0N2Y1MTNhYmNlMzQyY2UxZTJlYzJmZDk2MDBl -MzFiICpmbGV4LWNvbmZpcm0tbWFpbC4xLjExLjAueHBpCjA3MWU5ZTM3OGFkMDE3OWJmYWRi -MWJkYzY1MGE0OTQ1NGQyMDRhODMgKmZsZXgtY29uZmlybS1tYWlsLjEuMTIuMC54cGkKOWQ3 -YWExNTM0MThlYThmYmM4YmU3YmE2ZjU0Y2U4YTFjYjdlZTQ2OCAqZmxleC1jb25maXJtLW1h -aWwuMS45LjkueHBpCjgxNjg1NjNjYjI3NmVhNGY5YTJiNjMwYjlhMjA3ZDkwZmIxMTg1NmUg -KmZsZXgtY29uZmlybS1tYWlsLnhwaQo= ---------------26A45336F6C6196BD8BBA2A2 -Content-Type: application/json; - name="manifest.json" -Content-Transfer-Encoding: base64 -Content-Disposition: attachment; - filename="manifest.json" - -ewogICJtYW5pZmVzdF92ZXJzaW9uIjogMiwKICAiYXBwbGljYXRpb25zIjogewogICAgImdl -Y2tvIjogewogICAgICAiaWQiOiAiZmxleGlibGUtY29uZmlybS1tYWlsQGNsZWFyLWNvZGUu -Y29tIiwKICAgICAgInN0cmljdF9taW5fdmVyc2lvbiI6ICI2OC4wIgogICAgfQogIH0sCiAg -Im5hbWUiOiAiRmxleCBDb25maXJtIE1haWwiLAogICJkZXNjcmlwdGlvbiI6ICJDb25maXJt -IG1haWxhZGRyZXNzIGFuZCBhdHRhY2htZW50cyBiYXNlZCBvbiBmbGV4aWJsZSBydWxlcy4i -LAogICJ2ZXJzaW9uIjogIjIuMCIsCgogICJsZWdhY3kiOiB7CiAgICAidHlwZSI6ICJ4dWwi -LAogICAgIm9wdGlvbnMiOiB7CiAgICAgICJwYWdlIjogImNocm9tZTovL2NvbmZpcm0tbWFp -bC9jb250ZW50L3NldHRpbmcueHVsIiwKICAgICAgIm9wZW5faW5fdGFiIjogdHJ1ZQogICAg -fQogIH0KfQ== ---------------26A45336F6C6196BD8BBA2A2-- diff --git a/email/1.3.0/src/sample.msg b/email/1.3.0/src/sample.msg deleted file mode 100644 index ded03fdf23a72dbfbf3af272a4a0308473b0b0fd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9728 zcmeHM&2Jk;6n|^Cq)qC60BvbX-AzBBq{P_HSCjCOv~^Gs~7LMdRI>dGmhr z-n{o_X7lSW`~JN7WA|UwKs^+uM{7Ijg-z`mzjt``y`=ik_GoQw4bA(w@auDWhAgl{ zcUSlp7LaJteaL@z-*EjvKBAtp-SJZw(y?jFG3%wuYq_eKaf;=6JyX`LC8uOr3;LpI z+nIS&FBD6GaxbN4FN{uJoJo(LpGi+lO~=m7q~+*Dg{rkUp0^e=#d0iXEw(aL@wPj@ zUH!G&ckcJz-Swd7URu99e&f5}ZXNt-`ct_YANPG&w#~9LswX0yY1i<5Q|CwZw{wGM zvWe_qvM`h=3=E$c$z}%P{rw}kA#-G;e_$Z~ZlrUPjW?<{m>|-5X({`enPZVb%0)V7 ztx?^vHZ1^8Vk({L`^c=?MXSuSip3+HkxpNQzBqmKyPv<^d+3`o{jTkq4H0SA3sfbG z7HORF>Y1S;l_^F!RU^7YX_}=AEH{YL7rJf#Y1e---hYPvy$y@!&%8x%-QsQ0AJi1& zB0=M4FBk3ggu$;$o|)IP%ld>}v1~Jz;@B=V6%*qP6eXSdU;<1lM%5`Kj7-@s8ii~z zU$oVGt}>rU4knGfSt!Ku>twRS$+!XYIp*iiiczr2j*~6%3%A{)CK5bspFLJ6WkF7FWqLD4hk+Zy*wMt{FtC7wXBWIPYDvKO5s%Abhk~HS4rdh^I zwzOpObBx)Ht3~tLLb045RBv`JQ!*2Aj+qss*T^m`F4#_{>gZ?Fb7xcUPff2Hl}t5n zTqzlq5~n<#0+fia2+fFz)tfRXOVo2eUi&)r==kHUt+(JD1oED4jX3^0;@Dv?-@Mm* z|ITe~oOaaJ!}MT}cg6}cUzq+?bxtDuj`GKQ=>PknS3kIZ?m~YT9o}6(J~Dv%@@|(u zO5KWor;`a{|wRnM;TcdI%dKNVh-j@H#f+L)5(jgQh< zRo~z@8|q;-Kdc+<8KqD|eO2-QQ0otAJC*)P9uOwXe^Avu`-Evo@iV)U7t*@adb{}G z&#Qh9kJ-KgZ#%Tr{^RgI6TJ8Mami-XZvJg||9Besqq8$lczWBgQAqnw#qBn3f?tHR zHC6wlR*#aV{J@J7_A^!Ah{JGQzvnkE)y9Wuzw&d>Z`fBA|HcfS>J9OBa5z#rxMQ7$?c#LY1-$GM!~a*~TD8#%^%-~5bm`&+d>emti(EO3$Y zghdXoMT*a-4xd_=TatEwwrk#X7rxmQ!Olf=Oa1!XC*e#?bNpAP44;%uJ|XA1c9q|% zWOL2po&=wn<1Xu#zo$r3_}Ft?nBbF7o4?e7(XuVHV5QhcfYLo_JHkF!T z)8G5{?@IOmg|B35EdKcN{QDxylj@JP3idzf5BtmX$2nb!kF^TUf51=vpA_RmAF2LWt6=*>cbvau@sD_t;>+?M@Nxc<;p4vZ^zr5SA9pP&K71812x`9m zsha=G^v69u(V`Qyv;-(i*|#fPur`w_?wzrTh3 z_f?i9#m8KAE}&-kuIJ0oe@9rB6d!ZleE+Ga|CLnKz0cu$pUHPI={+!ByMN)Ef)pQf z9j{#$p?{STbBtSQf;z`vCp=&Do-9JqXzl(9Y<$ybm73W<&R6G2{+1}+e{=?x>i-(^ z2Iqg^Bmc|xzn&KG5qtP925P3imw91tN&FvUSyKJqVBVno=ib=|ECfEusz6?>i^rv-Cz2%w9>o&{Y#49if{pM wTlm<9kP7il0zQemInGBYxF_MRg8PE^{TFeAyB5xG$l>672*sb%GiibU0e5D^J^%m! diff --git a/outlook-exchange/1.0.0/src/app.py b/outlook-exchange/1.0.0/src/app.py index 43673a10..38d8b2cd 100644 --- a/outlook-exchange/1.0.0/src/app.py +++ b/outlook-exchange/1.0.0/src/app.py @@ -198,14 +198,13 @@ def send_email( account=account, subject=subject, body=body, - to_recipients=[ - Mailbox(email_address=address) for address in recipient.split(", ") - ], - cc_recipients=[ - Mailbox(email_address=address) for address in ccrecipient.split(", ") - ], + to_recipients=[] ) + for address in recipient.split(", "): + address = address.strip() + m.to_recipients.append(Mailbox(email_address=address)) + file_uids = str(attachments).split() if len(file_uids) > 0: for file_uid in file_uids: From ecf655f12e9f4c6a65e17a81ecc230e01ff95b95 Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 24 Aug 2023 17:22:41 +0200 Subject: [PATCH 052/259] Added a Report Generator to the Shuffle AI app --- shuffle-ai/1.0.0/api.yaml | 34 ++++++++++++++++++++++++ shuffle-ai/1.0.0/src/app.py | 53 +++++++++++++++++++++++++++++++++++++ shuffle-ai/1.0.0/upload.sh | 6 +++++ 3 files changed, 93 insertions(+) create mode 100755 shuffle-ai/1.0.0/upload.sh diff --git a/shuffle-ai/1.0.0/api.yaml b/shuffle-ai/1.0.0/api.yaml index 31fbf7d5..ae0ae960 100644 --- a/shuffle-ai/1.0.0/api.yaml +++ b/shuffle-ai/1.0.0/api.yaml @@ -38,6 +38,40 @@ actions: returns: schema: type: string + - name: generate_report + description: Input ANY kind of data in the format you want, and it will make an HTML report for you. This can be downloaded from the File location. + parameters: + - name: apikey + description: Your https://shuffler.io apikey + required: true + multiline: false + example: "" + schema: + type: string + - name: input_data + description: The text you want to be converted (ANY format) + required: true + multiline: true + example: "Bad IPs are 1.2.3.4 and there's no good way to format this. JSON works too!" + schema: + type: string + - name: report_title + description: The report title to be used in the report + required: true + multiline: true + example: "Statistics for October" + schema: + type: string + - name: report_name + description: The name of the HTML file + required: false + multiline: true + example: "statistics.html" + schema: + type: string + returns: + schema: + type: string - name: extract_text_from_pdf description: Returns text from a pdf parameters: diff --git a/shuffle-ai/1.0.0/src/app.py b/shuffle-ai/1.0.0/src/app.py index 9724a02f..4a76c673 100644 --- a/shuffle-ai/1.0.0/src/app.py +++ b/shuffle-ai/1.0.0/src/app.py @@ -44,6 +44,59 @@ def autoformat_text(self, apikey, text, formatting="auto"): return ret.text + def generate_report(self, apikey, input_data, report_title, report_name="generated_report.html"): + headers = { + "Authorization": "Bearer %s" % apikey, + } + + if not report_name: + report_name = "generated_report.html" + + if "." in report_name and not ".html" in report_name: + report_name = report_name.split(".")[0] + + if not "html" in report_name: + report_name = report_name + ".html" + + report_name = report_name.replace(" ", "_", -1) + + if not formatting: + formatting = "auto" + + output_formatting= "Format the following text into an HTML report with relevant graphs and tables. Title of the report should be {report_title}." + ret = requests.post( + "https://shuffler.io/api/v1/conversation", + json={ + "query": text, + "formatting": output_formatting, + "output_format": "formatting" + }, + headers=headers, + ) + + if ret.status_code != 200: + print(ret.text) + return { + "success": False, + "reason": "Status code for auto-formatter is not 200" + } + + # Make it into a shuffle file with self.set_files() + new_file = { + "name": report_name, + "data": ret.text, + } + + retdata = self.set_files([new_file]) + if retdata["success"]: + return retdata + + return { + "success": False, + "reason": "Failed to upload file" + } + + def extract_text_from_pdf(self, file_id): def extract_pdf_text(pdf_path): with open(pdf_path, 'rb') as file: diff --git a/shuffle-ai/1.0.0/upload.sh b/shuffle-ai/1.0.0/upload.sh new file mode 100755 index 00000000..33f84bac --- /dev/null +++ b/shuffle-ai/1.0.0/upload.sh @@ -0,0 +1,6 @@ + +gcloud run deploy shuffle-ai-1-0-0 \ + --region=europe-west2 \ + --max-instances=3 \ + --set-env-vars=SHUFFLE_APP_EXPOSED_PORT=8080,SHUFFLE_SWARM_CONFIG=run,SHUFFLE_LOGS_DISABLED=true --source=./ \ + --timeout=1800s From 552e116fc92ec492142dcb82d6cb9d54f5eca90c Mon Sep 17 00:00:00 2001 From: dhaval055 Date: Mon, 28 Aug 2023 11:49:29 +0530 Subject: [PATCH 053/259] added faster ioc parser --- shuffle-tools/1.2.0/api.yaml | 20 ++++++ shuffle-tools/1.2.0/src/app.py | 108 ++++++++++++++++++++++++++++++++- 2 files changed, 127 insertions(+), 1 deletion(-) diff --git a/shuffle-tools/1.2.0/api.yaml b/shuffle-tools/1.2.0/api.yaml index 66dff115..3829d460 100644 --- a/shuffle-tools/1.2.0/api.yaml +++ b/shuffle-tools/1.2.0/api.yaml @@ -264,6 +264,26 @@ actions: returns: schema: type: string + - name: quick_parse_ioc + description: Faster IOC parsing with few default types and concurrent processing. + parameters: + - name: file_id + description: file_id + required: true + multiline: false + example: "" + schema: + type: string + - name: input_type + description: By default cves,domains,ipv4s,ipv6s,md5s and urls will be parsed. + required: false + multiline: false + example: "md5s,ipv4s,domains" + schema: + type: string + returns: + schema: + type: string - name: parse_file_ioc description: Parse IOC's based on https://github.com/fhightower/ioc-finder parameters: diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index c8fdb99d..4994378a 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -37,6 +37,9 @@ import paramiko +import concurrent.futures +import multiprocessing + from walkoff_app_sdk.app_base import AppBase class Tools(AppBase): @@ -2386,7 +2389,7 @@ def run_ssh_command(self, host, port, user_name, private_key_file_id, password, except Exception as e: return {"success":"false","message":str(e)} else: - print("AUTH WITH PASSWORD") + #print("AUTH WITH PASSWORD") try: ssh_client.connect(hostname=host,username=user_name,port=port, password=str(password)) except Exception as e: @@ -2398,6 +2401,109 @@ def run_ssh_command(self, host, port, user_name, private_key_file_id, password, return {"success":"false","message":str(e)} return {"success":"true","output": stdout.read().decode(errors='ignore')} + + def split_list(self, arr, length): + + num_arrays = len(arr) // length + remainder = len(arr) % length + + arrays = [] + start = 0 + + for _ in range(num_arrays): + sub_array = arr[start:start+length] + arrays.append(sub_array) + start += length + + if remainder > 0: + sub_array = arr[start:] + arrays.append(sub_array) + + return arrays + + def split_text(self,text): + lines = text.split("\n") + num = 10 + new_text = "" + new_text_list = [] + for line in lines: + num -= 1 + new_text += line + "\n" + if num == 0: + new_text_list.append(new_text.strip("\n")) + num = 10 + new_text = "" + return new_text_list + + def _with_concurency(self,txt_data, ioc_types): + + cpu_count = multiprocessing.cpu_count() + #print("cpu count:",cpu_count) + start = time.perf_counter() + executor = concurrent.futures.ProcessPoolExecutor(cpu_count) + futures = [executor.submit(find_iocs, row, included_ioc_types=ioc_types) for row in txt_data] + results = [future.result() for future in futures] + #print("Total time taken:",time.perf_counter()-start) + return self._format_result(results) + + def _format_result(self,result): + final_result = {} + + for res in result: + for key,val in res.items(): + if key in final_result: + if isinstance(val, list) and len(val) > 0: + for i in val: + final_result[key].append(i) + elif isinstance(val, dict): + #print(key,":::",val) + if key in final_result: + if isinstance(val, dict): + for k,v in val.items(): + #print("k:",k,"v:",v) + val[k].append(v) + #print(val) + #final_result[key].append([i for i in val if len(val) > 0]) + else: + final_result[key] = val + + return final_result + + def quick_parse_ioc(self,file_id,input_type=None): + file_data = self.get_file(file_id) + + DEFAULT_IOC_TYPES = [ + "cves", + "domains", + "ipv4s", + "md5s", + "urls" + ] + + if input_type == "": + input_type = DEFAULT_IOC_TYPES + else: + input_type = input_type.split(",") + + if file_data.get("data"): + file_data = file_data["data"].decode() + else: + return {"success":"false","message":"file not found"} + + file_data = self.split_text(file_data) + + # if (len(input_list) > 50): + # start = time.perf_counter() + # input_list = self.split_list(input_list, 50) + # executor = concurrent.futures.ProcessPoolExecutor(4) + # futures = [executor.submit(self.with_concurency, row) for row in input_list] + # results = [future.result() for future in futures] + # print(" Overall Total time taken:",time.perf_counter()-start) + # return results + # else: + + res = self._with_concurency(file_data,ioc_types=input_type) + return res if __name__ == "__main__": From 95e45907d2766e6e12a71f218f40a998765d715f Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 30 Aug 2023 23:05:23 +0200 Subject: [PATCH 054/259] Updated subflow version to 1.1.0 in api.yaml --- shuffle-subflow/1.1.0/api.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shuffle-subflow/1.1.0/api.yaml b/shuffle-subflow/1.1.0/api.yaml index 4148c19a..0c64bda1 100644 --- a/shuffle-subflow/1.1.0/api.yaml +++ b/shuffle-subflow/1.1.0/api.yaml @@ -1,4 +1,4 @@ -app_version: 1.0.0 +app_version: 1.1.0 name: Shuffle Subflow description: The Shuffle Subflow app tags: From cdae647307d3527a2a9685e412bd8316eb450adc Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 21 Sep 2023 20:56:50 +0200 Subject: [PATCH 055/259] Added delete cache key and added base64 to Image --- shuffle-tools/1.2.0/api.yaml | 22 ++++++++++++++---- shuffle-tools/1.2.0/src/app.py | 42 ++++++++++++++++++++++------------ 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/shuffle-tools/1.2.0/api.yaml b/shuffle-tools/1.2.0/api.yaml index 3829d460..47974eb7 100644 --- a/shuffle-tools/1.2.0/api.yaml +++ b/shuffle-tools/1.2.0/api.yaml @@ -85,6 +85,19 @@ actions: returns: schema: type: string + - name: delete_cache_value + description: Delete a value saved to your organization in Shuffle + parameters: + - name: key + description: The key to delete + required: true + multiline: false + example: "timestamp" + schema: + type: string + returns: + schema: + type: string - name: send_sms_shuffle description: Send an SMS from Shuffle parameters: @@ -158,14 +171,14 @@ actions: skip_multicheck: true parameters: - name: input_list - description: The list to check + description: The list to filter from. Don't use .# into this. required: true multiline: false example: '[{"data": "1.2.3.4"}, {"data": "1.2.3.5"}]' schema: type: string - name: field - description: The field to check + description: The field to check in the input list required: true multiline: false example: "data" @@ -173,7 +186,7 @@ actions: type: string - name: check description: Type of check - required: true + required: false example: "equals" options: - equals @@ -199,7 +212,7 @@ actions: type: string - name: opposite description: Whether to add or to NOT add - required: true + required: false options: - False - True @@ -921,6 +934,7 @@ actions: options: - encode - decode + - "to image" schema: type: string - name: get_timestamp diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 4994378a..42b59f2a 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -1,4 +1,4 @@ -import asyncio +import hmac import datetime import json import time @@ -13,8 +13,6 @@ import hashlib from io import StringIO from contextlib import redirect_stdout -from liquid import Liquid -import liquid import random import string @@ -72,6 +70,23 @@ def base64_conversion(self, string, operation): encoded_string = str(encoded_bytes, "utf-8") return encoded_string + elif operation == "to image": + # Decode the base64 into an image and upload it as a file + decoded_bytes = base64.b64decode(a).encode("utf-8").decode("unicode_escape") + + file = { + "filename": member.split("/")[-1], + "data": decoded_bytes, + } + + fileret = self.set_files([file]) + filename = "base64_image.png" + value = {"success": True, "filename": filename, "file_id": fileret} + if len(fileret) == 1: + value = {"success": True, "filename": filename, "file_id": fileret[0]} + + return value + elif operation == "decode": try: decoded_bytes = base64.b64decode(string) @@ -510,7 +525,7 @@ def regex_replace( return re.sub(regex, replace_string, input_data) def execute_python(self, code): - self.logger.info(f"Python code {len(code)} {code}. If uuid, we'll try to download and use the file.") + self.logger.info(f"Python code {len(code)}. If uuid, we'll try to download and use the file.") if len(code) == 36 and "-" in code: filedata = self.get_file(code) @@ -539,6 +554,12 @@ def custom_print(*args, **kwargs): # Add globals in it too globals_copy = globals().copy() globals_copy["print"] = custom_print + + # Add self to globals_copy + if "self" in globals_copy: + del globals_copy["self"] + + globals_copy["self"] = self exec(code, globals_copy) s = f.getvalue() @@ -1907,6 +1928,9 @@ def change_cache_subkey(self, key, subkey, value, overwrite): self.logger.info("Value couldn't be parsed") return response.text + def delete_cache_value(self, key): + return self.delete_cache(key) + def get_cache_value(self, key): org_id = self.full_execution["workflow"]["execution_org"]["id"] url = "%s/api/v1/orgs/%s/get_cache" % (self.url, org_id) @@ -2492,19 +2516,9 @@ def quick_parse_ioc(self,file_id,input_type=None): file_data = self.split_text(file_data) - # if (len(input_list) > 50): - # start = time.perf_counter() - # input_list = self.split_list(input_list, 50) - # executor = concurrent.futures.ProcessPoolExecutor(4) - # futures = [executor.submit(self.with_concurency, row) for row in input_list] - # results = [future.result() for future in futures] - # print(" Overall Total time taken:",time.perf_counter()-start) - # return results - # else: res = self._with_concurency(file_data,ioc_types=input_type) return res - if __name__ == "__main__": Tools.run() From 85216e1ad5a4bba1ad7c4b2c69fb1e3b64b2f75e Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 21 Sep 2023 23:13:03 +0200 Subject: [PATCH 056/259] Fixed more in shuffle tools --- shuffle-tools/1.2.0/api.yaml | 20 --- shuffle-tools/1.2.0/src/app.py | 238 +++++++++++++++++++++------------ 2 files changed, 156 insertions(+), 102 deletions(-) diff --git a/shuffle-tools/1.2.0/api.yaml b/shuffle-tools/1.2.0/api.yaml index 47974eb7..eb8311f8 100644 --- a/shuffle-tools/1.2.0/api.yaml +++ b/shuffle-tools/1.2.0/api.yaml @@ -277,26 +277,6 @@ actions: returns: schema: type: string - - name: quick_parse_ioc - description: Faster IOC parsing with few default types and concurrent processing. - parameters: - - name: file_id - description: file_id - required: true - multiline: false - example: "" - schema: - type: string - - name: input_type - description: By default cves,domains,ipv4s,ipv6s,md5s and urls will be parsed. - required: false - multiline: false - example: "md5s,ipv4s,domains" - schema: - type: string - returns: - schema: - type: string - name: parse_file_ioc description: Parse IOC's based on https://github.com/fhightower/ioc-finder parameters: diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 42b59f2a..bc0e9f15 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -72,15 +72,22 @@ def base64_conversion(self, string, operation): elif operation == "to image": # Decode the base64 into an image and upload it as a file - decoded_bytes = base64.b64decode(a).encode("utf-8").decode("unicode_escape") + decoded_bytes = base64.b64decode(string) + # Make the bytes into unicode escaped bytes + # UnicodeDecodeError - 'utf-8' codec can't decode byte 0x89 in position 0: invalid start byte + try: + decoded_bytes = str(decoded_bytes, "utf-8") + except: + pass + + filename = "base64_image.png" file = { - "filename": member.split("/")[-1], + "filename": filename, "data": decoded_bytes, } fileret = self.set_files([file]) - filename = "base64_image.png" value = {"success": True, "filename": filename, "file_id": fileret} if len(fileret) == 1: value = {"success": True, "filename": filename, "file_id": fileret[0]} @@ -269,68 +276,6 @@ def parse(data): return "Invalid input" return return_value - # https://github.com/fhightower/ioc-finder - def parse_ioc(self, input_string, input_type="all"): - #if len(input_string) > 2500000 and (input_type == "" or input_type == "all"): - # return { - # "success": False, - # "reason": "Data too large (%d). Please reduce it below 2.5 Megabytes to use this action or specify the input type" % len(input_string) - # } - - # https://github.com/fhightower/ioc-finder/blob/6ff92a73a60e9233bf09b530ccafae4b4415b08a/ioc_finder/ioc_finder.py#L433 - ioc_types = ["domains", "urls", "email_addresses", "ipv6s", "ipv4s", "ipv4_cidrs", "md5s", "sha256s", "sha1s", "cves"] - input_string = str(input_string) - if input_type == "": - input_type = "all" - else: - input_type = input_type.split(",") - for item in input_type: - item = item.strip() - - ioc_types = input_type - - iocs = find_iocs(input_string, included_ioc_types=ioc_types) - newarray = [] - for key, value in iocs.items(): - if input_type != "all": - if key not in input_type: - continue - - if len(value) > 0: - for item in value: - # If in here: attack techniques. Shouldn't be 3 levels so no - # recursion necessary - if isinstance(value, dict): - for subkey, subvalue in value.items(): - if len(subvalue) > 0: - for subitem in subvalue: - data = { - "data": subitem, - "data_type": "%s_%s" % (key[:-1], subkey), - } - if data not in newarray: - newarray.append(data) - else: - data = {"data": item, "data_type": key[:-1]} - if data not in newarray: - newarray.append(data) - - # Reformatting IP - for item in newarray: - if "ip" in item["data_type"]: - item["data_type"] = "ip" - try: - item["is_private_ip"] = ipaddress.ip_address(item["data"]).is_private - except: - self.logger.info("Error parsing %s" % item["data"]) - - try: - newarray = json.dumps(newarray) - except json.decoder.JSONDecodeError as e: - return "Failed to parse IOC's: %s" % e - - return newarray - def parse_list(self, items, splitter="\n"): if splitter == "": splitter = "\n" @@ -556,10 +501,14 @@ def custom_print(*args, **kwargs): globals_copy["print"] = custom_print # Add self to globals_copy - if "self" in globals_copy: - del globals_copy["self"] + for key, value in locals().items(): + if key not in all_globals: + all_globals[key] = value + + #if "self" in globals_copy: + # del globals_copy["self"] + #globals_copy["self"] = self - globals_copy["self"] = self exec(code, globals_copy) s = f.getvalue() @@ -573,13 +522,19 @@ def custom_print(*args, **kwargs): try: return { "success": True, - "message": s.strip(), + "message": json.loads(s.strip()), } except Exception as e: - return { - "success": True, - "message": s, - } + try: + return { + "success": True, + "message": s.strip(), + } + except Exception as e: + return { + "success": True, + "message": s, + } except Exception as e: return { @@ -2493,15 +2448,18 @@ def _format_result(self,result): return final_result - def quick_parse_ioc(self,file_id,input_type=None): + def quick_parse_ioc(self, file_id, input_type=None): file_data = self.get_file(file_id) DEFAULT_IOC_TYPES = [ - "cves", - "domains", - "ipv4s", - "md5s", - "urls" + "cves", + "domains", + "ipv4s", + "md5s", + "sha1s", + "sha256s", + "urls" + "email_addresses", ] if input_type == "": @@ -2515,10 +2473,126 @@ def quick_parse_ioc(self,file_id,input_type=None): return {"success":"false","message":"file not found"} file_data = self.split_text(file_data) - - - res = self._with_concurency(file_data,ioc_types=input_type) + res = self._with_concurency(file_data, ioc_types=input_type) return res + def parse_ioc_new(self, input_string, input_type="all"): + ioc_types = ["domains", "urls", "email_addresses", "ipv6s", "ipv4s", "ipv4_cidrs", "md5s", "sha256s", "sha1s", "cves"] + input_string = str(input_string) + #if input_type == "": + # input_type = "all" + #else: + # input_type = input_type.split(",") + # for item in input_type: + # item = item.strip() + + # ioc_types = input_type + + file_data = self.split_text(input_string) + iocs = self._with_concurency(file_data, ioc_types=input_type) + #return res + #iocs = find_iocs(input_string, included_ioc_types=ioc_types) + newarray = [] + for key, value in iocs.items(): + if input_type != "all": + if key not in input_type: + continue + + if len(value) > 0: + for item in value: + # If in here: attack techniques. Shouldn't be 3 levels so no + # recursion necessary + if isinstance(value, dict): + for subkey, subvalue in value.items(): + if len(subvalue) > 0: + for subitem in subvalue: + data = { + "data": subitem, + "data_type": "%s_%s" % (key[:-1], subkey), + } + if data not in newarray: + newarray.append(data) + else: + data = {"data": item, "data_type": key[:-1]} + if data not in newarray: + newarray.append(data) + + # Reformatting IP + for item in newarray: + if "ip" in item["data_type"]: + item["data_type"] = "ip" + try: + item["is_private_ip"] = ipaddress.ip_address(item["data"]).is_private + except: + self.logger.info("Error parsing %s" % item["data"]) + + try: + newarray = json.dumps(newarray) + except json.decoder.JSONDecodeError as e: + return "Failed to parse IOC's: %s" % e + + return newarray + + def parse_ioc(self, input_string, input_type="all"): + #if len(input_string) > 2500000 and (input_type == "" or input_type == "all"): + # return { + # "success": False, + # "reason": "Data too large (%d). Please reduce it below 2.5 Megabytes to use this action or specify the input type" % len(input_string) + # } + + # https://github.com/fhightower/ioc-finder/blob/6ff92a73a60e9233bf09b530ccafae4b4415b08a/ioc_finder/ioc_finder.py#L433 + ioc_types = ["domains", "urls", "email_addresses", "ipv6s", "ipv4s", "ipv4_cidrs", "md5s", "sha256s", "sha1s", "cves"] + input_string = str(input_string) + if input_type == "": + input_type = "all" + else: + input_type = input_type.split(",") + for item in input_type: + item = item.strip() + + ioc_types = input_type + + iocs = find_iocs(input_string, included_ioc_types=ioc_types) + newarray = [] + for key, value in iocs.items(): + if input_type != "all": + if key not in input_type: + continue + + if len(value) > 0: + for item in value: + # If in here: attack techniques. Shouldn't be 3 levels so no + # recursion necessary + if isinstance(value, dict): + for subkey, subvalue in value.items(): + if len(subvalue) > 0: + for subitem in subvalue: + data = { + "data": subitem, + "data_type": "%s_%s" % (key[:-1], subkey), + } + if data not in newarray: + newarray.append(data) + else: + data = {"data": item, "data_type": key[:-1]} + if data not in newarray: + newarray.append(data) + + # Reformatting IP + for item in newarray: + if "ip" in item["data_type"]: + item["data_type"] = "ip" + try: + item["is_private_ip"] = ipaddress.ip_address(item["data"]).is_private + except: + self.logger.info("Error parsing %s" % item["data"]) + + try: + newarray = json.dumps(newarray) + except json.decoder.JSONDecodeError as e: + return "Failed to parse IOC's: %s" % e + + return newarray + if __name__ == "__main__": Tools.run() From 0617a78cad48f6d0e865de5a99a94a12b3bb0f69 Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 25 Sep 2023 10:32:02 +0200 Subject: [PATCH 057/259] Missed last push for all globals.... --- shuffle-tools/1.2.0/src/app.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index bc0e9f15..ffb3f4d6 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -501,13 +501,11 @@ def custom_print(*args, **kwargs): globals_copy["print"] = custom_print # Add self to globals_copy - for key, value in locals().items(): - if key not in all_globals: - all_globals[key] = value + for key, value in locals().copy().items(): + if key not in globals_copy: + globals_copy[key] = value - #if "self" in globals_copy: - # del globals_copy["self"] - #globals_copy["self"] = self + globals_copy["self"] = self exec(code, globals_copy) From 06d68ee998909c9202458292fda4fa424b7f1d4d Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 25 Sep 2023 12:19:19 +0200 Subject: [PATCH 058/259] Fixed some failover issues with auth/exec id for subflows --- shuffle-subflow/1.0.0/src/app.py | 6 ++++++ shuffle-subflow/1.1.0/src/app.py | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/shuffle-subflow/1.0.0/src/app.py b/shuffle-subflow/1.0.0/src/app.py index 2fc46527..82253179 100644 --- a/shuffle-subflow/1.0.0/src/app.py +++ b/shuffle-subflow/1.0.0/src/app.py @@ -172,6 +172,12 @@ def run_subflow(self, user_apikey, workflow, argument, source_workflow="", sourc else: print("No startnode") + if len(self.full_execution["execution_id"]) > 0 and self.full_execution["execution_id"] != source_execution: + params["source_execution"] = self.full_execution["execution_id"] + + if len(self.full_execution["authorization"]) > 0 and self.full_execution["authorization"] != source_auth: + params["source_auth"] = self.full_execution["authorization"] + if len(str(backend_url)) > 0: url = "%s/api/v1/workflows/%s/execute" % (backend_url, workflow) print("[INFO] Changed URL to %s for this execution" % url) diff --git a/shuffle-subflow/1.1.0/src/app.py b/shuffle-subflow/1.1.0/src/app.py index f1fa452a..a02ae79e 100644 --- a/shuffle-subflow/1.1.0/src/app.py +++ b/shuffle-subflow/1.1.0/src/app.py @@ -174,6 +174,12 @@ def run_subflow(self, user_apikey, workflow, argument, source_workflow="", sourc else: print("No startnode") + if len(self.full_execution["execution_id"]) > 0 and self.full_execution["execution_id"] != source_execution: + params["source_execution"] = self.full_execution["execution_id"] + + if len(self.full_execution["authorization"]) > 0 and self.full_execution["authorization"] != source_auth: + params["source_auth"] = self.full_execution["authorization"] + if len(str(backend_url)) > 0: url = "%s/api/v1/workflows/%s/execute" % (backend_url, workflow) print("[INFO] Changed URL to %s for this execution" % url) From fbe92b4da8bf769d90fdcb8c2a1ac2bf0362aab5 Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 28 Sep 2023 13:16:16 +0200 Subject: [PATCH 059/259] Fixed an equals any of issue where checks are not handled properly --- shuffle-tools/1.2.0/src/app.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index ffb3f4d6..e24b8d46 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -656,7 +656,7 @@ def filter_list(self, input_list, field, check, value, opposite): self.logger.info("Checklist and tmp: %s - %s" % (checklist, tmp)) found = False for subcheck in checklist: - subcheck = subcheck.strip() + subcheck = str(subcheck).strip() #ext.lower().strip() == value.lower().strip() if type(tmp) == list and subcheck in tmp: @@ -667,8 +667,17 @@ def filter_list(self, input_list, field, check, value, opposite): new_list.append(item) found = True break + elif type(tmp) == int and str(tmp) == subcheck: + new_list.append(item) + found = True + break else: - print("Nothing matching") + if str(tmp) == str(subcheck): + new_list.append(item) + found = True + break + else: + print("Nothing matching") if not found: failed_list.append(item) From f05232074cb5ae23789c7f4caa2187659d18fb66 Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 28 Sep 2023 13:24:21 +0200 Subject: [PATCH 060/259] Minor fix to the email app to make it easier to send unauthenticated email --- email/1.3.0/api.yaml | 4 ++-- email/1.3.0/src/app.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/email/1.3.0/api.yaml b/email/1.3.0/api.yaml index aa29bc5b..40998adf 100644 --- a/email/1.3.0/api.yaml +++ b/email/1.3.0/api.yaml @@ -52,14 +52,14 @@ actions: description: The SMTP login username multiline: false example: "frikky@shuffler.io" - required: true + required: false schema: type: string - name: password description: The password to log in with SMTP multiline: false example: "******************" - required: true + required: false schema: type: string - name: smtp_host diff --git a/email/1.3.0/src/app.py b/email/1.3.0/src/app.py index b52bd92f..8ed2cdce 100644 --- a/email/1.3.0/src/app.py +++ b/email/1.3.0/src/app.py @@ -95,7 +95,7 @@ def send_email_smtp( else: s.starttls() - if len(username) > 0 or len(password) > 0: + if len(username) > 1 or len(password) > 1: try: s.login(username, password) except smtplib.SMTPAuthenticationError as e: From 52aa84da416d91bb8196ee7da3859d3915687f42 Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 11 Oct 2023 17:37:02 +0200 Subject: [PATCH 061/259] Updated header management for http app --- http/1.3.0/src/app.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/http/1.3.0/src/app.py b/http/1.3.0/src/app.py index 6ed58694..544f02a8 100755 --- a/http/1.3.0/src/app.py +++ b/http/1.3.0/src/app.py @@ -63,12 +63,8 @@ def splitheaders(self, headers): split_headers = headers.split("\n") self.logger.info(split_headers) for header in split_headers: - if ": " in header: - splititem = ": " - elif ":" in header: + if ":" in header: splititem = ":" - elif "= " in header: - splititem = "= " elif "=" in header: splititem = "=" else: @@ -76,8 +72,8 @@ def splitheaders(self, headers): continue splitheader = header.split(splititem) - if len(splitheader) == 2: - parsed_headers[splitheader[0]] = splitheader[1] + if len(splitheader) >= 2: + parsed_headers[splitheader[0]] = splititem.join(splitheader[1:]) else: self.logger.info("Skipping header %s with split %s cus only one item" % (header, splititem)) continue From f3657fd59a4fdcfcc6deb1a43a981be3aba61aab Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 20 Oct 2023 14:10:37 +0200 Subject: [PATCH 062/259] Added the possibility of the http app DELETE action to have a body --- email/1.3.0/src/app.py | 2 +- http/1.3.0/src/app.py | 3 +- http/1.4.0/Dockerfile | 27 ++ http/1.4.0/api.yaml | 529 ++++++++++++++++++++++++++++++++++++ http/1.4.0/requirements.txt | 2 + http/1.4.0/src/app.py | 460 +++++++++++++++++++++++++++++++ 6 files changed, 1021 insertions(+), 2 deletions(-) create mode 100644 http/1.4.0/Dockerfile create mode 100644 http/1.4.0/api.yaml create mode 100644 http/1.4.0/requirements.txt create mode 100755 http/1.4.0/src/app.py diff --git a/email/1.3.0/src/app.py b/email/1.3.0/src/app.py index 8ed2cdce..62b3a26d 100644 --- a/email/1.3.0/src/app.py +++ b/email/1.3.0/src/app.py @@ -75,7 +75,7 @@ def send_email_shuffle(self, apikey, recipients, subject, body): return requests.post(url, headers=headers, json=data).text def send_email_smtp( - self, username, password, smtp_host, recipient, subject, body, smtp_port, attachments="", ssl_verify="True", body_type="html" + self, smtp_host, recipient, subject, body, smtp_port, attachments="", username="", password="", ssl_verify="True", body_type="html" ): if type(smtp_port) == str: try: diff --git a/http/1.3.0/src/app.py b/http/1.3.0/src/app.py index 544f02a8..12742c99 100755 --- a/http/1.3.0/src/app.py +++ b/http/1.3.0/src/app.py @@ -26,6 +26,7 @@ def __init__(self, redis, logger, console_logger=None): # This is dangerously fun :) # Do we care about arbitrary code execution here? + # Probably not huh def curl(self, statement): process = subprocess.Popen(statement, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, shell=True) stdout = process.communicate() @@ -73,7 +74,7 @@ def splitheaders(self, headers): splitheader = header.split(splititem) if len(splitheader) >= 2: - parsed_headers[splitheader[0]] = splititem.join(splitheader[1:]) + parsed_headers[splitheader[0].strip()] = splititem.join(splitheader[1:]).strip() else: self.logger.info("Skipping header %s with split %s cus only one item" % (header, splititem)) continue diff --git a/http/1.4.0/Dockerfile b/http/1.4.0/Dockerfile new file mode 100644 index 00000000..9bbc5110 --- /dev/null +++ b/http/1.4.0/Dockerfile @@ -0,0 +1,27 @@ +# Base our app image off of the WALKOFF App SDK image +FROM frikky/shuffle:app_sdk as base + +# We're going to stage away all of the bloat from the build tools so lets create a builder stage +FROM base as builder + +# Install all alpine build tools needed for our pip installs +RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev + +# Install all of our pip packages in a single directory that we can copy to our base image later +RUN mkdir /install +WORKDIR /install +COPY requirements.txt /requirements.txt +RUN pip install --prefix="/install" -r /requirements.txt + +# Switch back to our base image and copy in all of our built packages and source code +FROM base +COPY --from=builder /install /usr/local +COPY src /app +RUN apk add curl + +# Install any binary dependencies needed in our final image +# RUN apk --no-cache add --update my_binary_dependency + +# Finally, lets run our app! +WORKDIR /app +CMD python app.py --log-level DEBUG diff --git a/http/1.4.0/api.yaml b/http/1.4.0/api.yaml new file mode 100644 index 00000000..cad10c2c --- /dev/null +++ b/http/1.4.0/api.yaml @@ -0,0 +1,529 @@ +walkoff_version: 1.4.0 +app_version: 1.4.0 +name: http +description: HTTP app +tags: + - Testing + - HTTP +categories: + - Other + - HTTP +contact_info: + name: "@frikkylikeme" + url: https://github.com/frikky + email: "frikky@shuffler.io" +actions: + - name: GET + description: Runs a GET request towards the specified endpoint + parameters: + - name: url + description: The URL to get + multiline: false + example: "https://example.com" + required: true + schema: + type: string + - name: headers + description: Headers to use + multiline: true + required: false + example: "Content-Type: application/json" + schema: + type: string + - name: username + description: The username to use + multiline: false + required: false + example: "Username" + schema: + type: string + - name: password + description: The password to use + multiline: false + required: false + example: "*****" + schema: + type: string + - name: verify + description: Check certificate + multiline: false + options: + - false + - true + required: false + example: "false" + schema: + type: bool + - name: http_proxy + description: Add a HTTP proxy + multiline: false + required: false + example: "http://192.168.0.1:8080" + schema: + type: bool + - name: https_proxy + description: Add a HTTPS proxy + multiline: false + required: false + example: "http://192.168.0.1:8080" + schema: + type: bool + - name: timeout + description: Add a timeout for the request, in seconds + multiline: false + required: false + example: "10" + schema: + type: bool + - name: to_file + description: Makes the response into a file, and returns it as an ID + multiline: false + required: false + options: + - false + - true + example: "true" + schema: + type: bool + returns: + schema: + type: string + example: "404 NOT FOUND" + - name: POST + description: Runs a POST request towards the specified endpoint + parameters: + - name: url + description: The URL to post to + multiline: false + example: "https://example.com" + required: true + schema: + type: string + - name: body + description: The body to use + multiline: true + example: "{\n\t'json': 'blob'\n}" + required: false + schema: + type: string + - name: headers + description: Headers to use + multiline: true + required: false + example: "Content-Type: application/json" + schema: + type: string + - name: username + description: The username to use + multiline: false + required: false + example: "Username" + schema: + type: string + - name: password + description: The password to use + multiline: false + required: false + example: "*****" + schema: + type: string + - name: verify + description: Whether to check the certificate or not + multiline: false + required: false + options: + - false + - true + example: "false" + schema: + type: bool + - name: http_proxy + description: Add a HTTP proxy + multiline: false + required: false + example: "http://192.168.0.1:8080" + schema: + type: bool + - name: https_proxy + description: Add a HTTPS proxy + multiline: false + required: false + example: "http://192.168.0.1:8080" + schema: + type: bool + - name: timeout + description: Add a timeout for the request, in seconds + multiline: false + required: false + example: "10" + schema: + type: bool + returns: + schema: + type: string + example: "404 NOT FOUND" + - name: PATCH + description: Runs a PATCH request towards the specified endpoint + parameters: + - name: url + description: The URL to post to + multiline: false + example: "https://example.com" + required: true + schema: + type: string + - name: body + description: The body to use + multiline: true + example: "{\n\t'json': 'blob'\n}" + required: false + schema: + type: string + - name: headers + description: Headers to use + multiline: true + required: false + example: "Content-Type: application/json" + schema: + type: string + - name: username + description: The username to use + multiline: false + required: false + example: "Username" + schema: + type: string + - name: password + description: The password to use + multiline: false + required: false + example: "*****" + schema: + type: string + - name: verify + description: Whether to check the certificate or not + multiline: false + required: false + options: + - false + - true + example: "false" + schema: + type: bool + - name: http_proxy + description: Add a HTTP proxy + multiline: false + required: false + example: "http://192.168.0.1:8080" + schema: + type: bool + - name: https_proxy + description: Add a HTTPS proxy + multiline: false + required: false + example: "http://192.168.0.1:8080" + schema: + type: bool + - name: timeout + description: Add a timeout for the request, in seconds + multiline: false + required: false + example: "10" + schema: + type: bool + returns: + schema: + type: string + example: "404 NOT FOUND" + - name: PUT + description: Runs a PUT request towards the specified endpoint + parameters: + - name: url + description: The URL to PUT to + multiline: false + example: "https://example.com" + required: true + schema: + type: string + - name: body + description: The body to use + multiline: true + example: "{\n\t'json': 'blob'\n}" + required: false + schema: + type: string + - name: headers + description: Headers to use + multiline: true + required: false + example: "Content-Type: application/json" + schema: + type: string + - name: username + description: The username to use + multiline: false + required: false + example: "Username" + schema: + type: string + - name: password + description: The password to use + multiline: false + required: false + example: "*****" + schema: + type: string + - name: verify + description: Whether to check the certificate or not + multiline: false + required: false + options: + - false + - true + example: "false" + schema: + type: bool + - name: http_proxy + description: Add a HTTP proxy + multiline: false + required: false + example: "http://192.168.0.1:8080" + schema: + type: bool + - name: https_proxy + description: Add a HTTPS proxy + multiline: false + required: false + example: "http://192.168.0.1:8080" + schema: + type: bool + - name: timeout + description: Add a timeout for the request, in seconds + multiline: false + required: false + example: "10" + schema: + type: bool + returns: + schema: + type: string + example: "404 NOT FOUND" + - name: DELETE + description: Runs a DELETE request towards the specified endpoint + parameters: + - name: url + description: The URL to post to + multiline: false + example: "https://example.com" + required: true + schema: + type: string + - name: body + description: The body to use + multiline: true + example: "{\n\t'json': 'blob'\n}" + required: false + schema: + type: string + - name: headers + description: Headers to use + multiline: true + required: false + example: "Content-Type: application/json" + schema: + type: string + - name: username + description: The username to use + multiline: false + required: false + example: "Username" + schema: + type: string + - name: password + description: The password to use + multiline: false + required: false + example: "*****" + schema: + type: string + - name: verify + description: Whether to check the certificate or not + multiline: false + required: false + options: + - true + - false + example: "false" + schema: + type: bool + - name: http_proxy + description: Add a HTTP proxy + multiline: false + required: false + example: "http://192.168.0.1:8080" + schema: + type: bool + - name: https_proxy + description: Add a HTTPS proxy + multiline: false + required: false + example: "http://192.168.0.1:8080" + schema: + type: bool + - name: timeout + description: Add a timeout for the request, in seconds + multiline: false + required: false + example: "10" + schema: + type: bool + returns: + schema: + type: string + example: "404 NOT FOUND" + - name: HEAD + description: Runs a HEAD request towards the specified endpoint + parameters: + - name: url + description: The URL to HEAD to + multiline: false + example: "https://example.com" + required: true + schema: + type: string + - name: headers + description: Headers to use + multiline: true + required: false + example: "Content-Type: application/json" + schema: + type: string + - name: username + description: The username to use + multiline: false + required: false + example: "Username" + schema: + type: string + - name: password + description: The password to use + multiline: false + required: false + example: "*****" + schema: + type: string + - name: verify + description: Whether to check the certificate or not + multiline: false + required: false + options: + - false + - true + example: "false" + schema: + type: bool + - name: http_proxy + description: Add a HTTP proxy + multiline: false + required: false + example: "http://192.168.0.1:8080" + schema: + type: bool + - name: https_proxy + description: Add a HTTPS proxy + multiline: false + required: false + example: "http://192.168.0.1:8080" + schema: + type: bool + - name: timeout + description: Add a timeout for the request, in seconds + multiline: false + required: false + example: "10" + schema: + type: bool + returns: + schema: + type: string + example: "404 NOT FOUND" + - name: OPTIONS + description: Runs a OPTIONS request towards the specified endpoint + parameters: + - name: url + description: The URL to HEAD to + multiline: false + example: "https://example.com" + required: true + schema: + type: string + - name: headers + description: Headers to use + multiline: true + required: false + example: "Content-Type: application/json" + schema: + type: string + - name: username + description: The username to use + multiline: false + required: false + example: "Username" + schema: + type: string + - name: password + description: The password to use + multiline: false + required: false + example: "*****" + schema: + type: string + - name: verify + description: Whether to check the certificate or not + multiline: false + required: false + options: + - false + - true + example: "false" + schema: + type: bool + - name: http_proxy + description: Add a HTTP proxy + multiline: false + required: false + example: "http://192.168.0.1:8080" + schema: + type: bool + - name: https_proxy + description: Add a HTTPS proxy + multiline: false + required: false + example: "http://192.168.0.1:8080" + schema: + type: bool + - name: timeout + description: Add a timeout for the request, in seconds + multiline: false + required: false + example: "10" + schema: + type: bool + returns: + schema: + type: string + example: "404 NOT FOUND" + - name: curl + description: Run a curl command + parameters: + - name: statement + description: The curl command to run + multiline: true + example: "curl https://example.com" + required: true + schema: + type: string + returns: + schema: + type: string +large_image: data:image/png;base64,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 diff --git a/http/1.4.0/requirements.txt b/http/1.4.0/requirements.txt new file mode 100644 index 00000000..ae3e5391 --- /dev/null +++ b/http/1.4.0/requirements.txt @@ -0,0 +1,2 @@ +uncurl==0.0.10 +requests==2.25.1 \ No newline at end of file diff --git a/http/1.4.0/src/app.py b/http/1.4.0/src/app.py new file mode 100755 index 00000000..ab37aa24 --- /dev/null +++ b/http/1.4.0/src/app.py @@ -0,0 +1,460 @@ +import time +import json +import ast +import random +import socket +import uncurl +import asyncio +import requests +import subprocess + +from walkoff_app_sdk.app_base import AppBase + +class HTTP(AppBase): + __version__ = "1.3.0" + app_name = "http" + + def __init__(self, redis, logger, console_logger=None): + print("INIT") + """ + Each app should have this __init__ to set up Redis and logging. + :param redis: + :param logger: + :param console_logger: + """ + super().__init__(redis, logger, console_logger) + + # This is dangerously fun :) + # Do we care about arbitrary code execution here? + # Probably not huh + def curl(self, statement): + process = subprocess.Popen(statement, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, shell=True) + stdout = process.communicate() + item = "" + if len(stdout[0]) > 0: + print("Succesfully ran bash!") + item = stdout[0] + else: + print("FAILED to run bash!") + item = stdout[1] + + try: + ret = item.decode("utf-8") + return ret + except: + return item + + return item + #try: + # if not statement.startswith("curl "): + # statement = "curl %s" % statement + + # data = uncurl.parse(statement) + # request = eval(data) + # if isinstance(request, requests.models.Response): + # return request.text + # else: + # return "Unable to parse the curl parameter. Remember to start with curl " + #except: + # return "An error occurred during curl parsing" + + def splitheaders(self, headers): + parsed_headers = {} + if headers: + split_headers = headers.split("\n") + self.logger.info(split_headers) + for header in split_headers: + if ":" in header: + splititem = ":" + elif "=" in header: + splititem = "=" + else: + self.logger.info("Skipping header %s as its invalid" % header) + continue + + splitheader = header.split(splititem) + if len(splitheader) >= 2: + parsed_headers[splitheader[0].strip()] = splititem.join(splitheader[1:]).strip() + else: + self.logger.info("Skipping header %s with split %s cus only one item" % (header, splititem)) + continue + + return parsed_headers + + def checkverify(self, verify): + if str(verify).lower().strip() == "false": + return False + elif verify == None: + return False + elif verify: + return True + elif not verify: + return False + else: + return True + + def checkbody(self, body): + # Indicates json + if isinstance(body, str): + if body.strip().startswith("{"): + body = json.dumps(ast.literal_eval(body)) + + + # Not sure if loading is necessary + # Seemed to work with plain string into data=body too, and not parsed json=body + #try: + # body = json.loads(body) + #except json.decoder.JSONDecodeError as e: + # return body + + return body + else: + return body + + if isinstance(body, dict) or isinstance(body, list): + try: + body = json.dumps(body) + except: + return body + + return body + + def fix_url(self, url): + # Random bugs seen by users + if "hhttp" in url: + url = url.replace("hhttp", "http") + + if "http:/" in url and not "http://" in url: + url = url.replace("http:/", "http://", -1) + if "https:/" in url and not "https://" in url: + url = url.replace("https:/", "https://", -1) + if "http:///" in url: + url = url.replace("http:///", "http://", -1) + if "https:///" in url: + url = url.replace("https:///", "https://", -1) + if not "http://" in url and not "http" in url: + url = f"http://{url}" + + return url + + def return_file(self, requestdata): + filedata = { + "filename": "response.txt", + "data": requestdata, + } + fileret = self.set_files([filedata]) + if len(fileret) == 1: + return {"success": True, "file_id": fileret[0]} + + return fileret + + def prepare_response(self, request): + try: + parsedheaders = {} + for key, value in request.headers.items(): + parsedheaders[key] = value + + cookies = {} + if request.cookies: + for key, value in request.cookies.items(): + cookies[key] = value + + + jsondata = request.text + try: + jsondata = json.loads(jsondata) + except: + pass + + return json.dumps({ + "success": True, + "status": request.status_code, + "url": request.url, + "headers": parsedheaders, + "body": jsondata, + "cookies":cookies, + }) + except Exception as e: + print(f"[WARNING] Failed in request: {e}") + return request.text + + def GET(self, url, headers="", username="", password="", verify=True, http_proxy="", https_proxy="", timeout=5, to_file=False): + url = self.fix_url(url) + + parsed_headers = self.splitheaders(headers) + parsed_headers["User-Agent"] = "Shuffle Automation" + verify = self.checkverify(verify) + proxies = None + if http_proxy: + proxies["http"] = http_proxy + if https_proxy: + proxies["https"] = https_proxy + + auth=None + if username or password: + # Shouldn't be used if authorization headers exist + if "Authorization" in parsed_headers: + #print("Found authorization - skipping username & pw") + pass + else: + auth = requests.auth.HTTPBasicAuth(username, password) + + if not timeout: + timeout = 5 + if timeout: + timeout = int(timeout) + + if to_file == "true": + to_file = True + else: + to_file = False + + request = requests.get(url, headers=parsed_headers, auth=auth, verify=verify, proxies=proxies, timeout=timeout) + if not to_file: + return self.prepare_response(request) + + return self.return_file(request.text) + + def POST(self, url, headers="", body="", username="", password="", verify=True, http_proxy="", https_proxy="", timeout=5, to_file=False): + url = self.fix_url(url) + + parsed_headers = self.splitheaders(headers) + parsed_headers["User-Agent"] = "Shuffle Automation" + verify = self.checkverify(verify) + body = self.checkbody(body) + proxies = None + if http_proxy: + proxies["http"] = http_proxy + if https_proxy: + proxies["https"] = https_proxy + + auth=None + if username or password: + # Shouldn't be used if authorization headers exist + if "Authorization" in parsed_headers: + #print("Found authorization - skipping username & pw") + pass + else: + auth = requests.auth.HTTPBasicAuth(username, password) + + if not timeout: + timeout = 5 + if timeout: + timeout = int(timeout) + + if to_file == "true": + to_file = True + else: + to_file = False + + request = requests.post(url, headers=parsed_headers, auth=auth, data=body, verify=verify, proxies=proxies, timeout=timeout) + if not to_file: + return self.prepare_response(request) + + return self.return_file(request.text) + + def PUT(self, url, headers="", body="", username="", password="", verify=True, http_proxy="", https_proxy="", timeout=5, to_file=False): + url = self.fix_url(url) + + parsed_headers = self.splitheaders(headers) + parsed_headers["User-Agent"] = "Shuffle Automation" + verify = self.checkverify(verify) + body = self.checkbody(body) + proxies = None + if http_proxy: + proxies["http"] = http_proxy + if https_proxy: + proxies["https"] = https_proxy + + + auth=None + if username or password: + # Shouldn't be used if authorization headers exist + if "Authorization" in parsed_headers: + #print("Found authorization - skipping username & pw") + pass + else: + auth = requests.auth.HTTPBasicAuth(username, password) + + if not timeout: + timeout = 5 + if timeout: + timeout = int(timeout) + + if to_file == "true": + to_file = True + else: + to_file = False + + request = requests.put(url, headers=parsed_headers, auth=auth, data=body, verify=verify, proxies=proxies, timeout=timeout) + if not to_file: + return self.prepare_response(request) + + return self.return_file(request.text) + + def PATCH(self, url, headers="", body="", username="", password="", verify=True, http_proxy="", https_proxy="", timeout=5, to_file=False): + url = self.fix_url(url) + + parsed_headers = self.splitheaders(headers) + parsed_headers["User-Agent"] = "Shuffle Automation" + verify = self.checkverify(verify) + body = self.checkbody(body) + proxies = None + if http_proxy: + proxies["http"] = http_proxy + if https_proxy: + proxies["https"] = https_proxy + + auth=None + if username or password: + # Shouldn't be used if authorization headers exist + if "Authorization" in parsed_headers: + #print("Found authorization - skipping username & pw") + pass + else: + auth = requests.auth.HTTPBasicAuth(username, password) + + if to_file == "true": + to_file = True + else: + to_file = False + + request = requests.patch(url, headers=parsed_headers, data=body, auth=auth, verify=verify, proxies=proxies, timeout=timeout) + if not to_file: + return self.prepare_response(request) + + return self.return_file(request.text) + + def DELETE(self, url, headers="", body="", username="", password="", verify=True, http_proxy="", https_proxy="", timeout=5, to_file=False): + url = self.fix_url(url) + + parsed_headers = self.splitheaders(headers) + parsed_headers["User-Agent"] = "Shuffle Automation" + verify = self.checkverify(verify) + body = self.checkbody(body) + proxies = None + if http_proxy: + proxies["http"] = http_proxy + if https_proxy: + proxies["https"] = https_proxy + + auth=None + if username or password: + # Shouldn't be used if authorization headers exist + if "Authorization" in parsed_headers: + #print("Found authorization - skipping username & pw") + pass + else: + auth = requests.auth.HTTPBasicAuth(username, password) + + if not timeout: + timeout = 5 + if timeout: + timeout = int(timeout) + + if to_file == "true": + to_file = True + else: + to_file = False + + request = requests.delete(url, headers=parsed_headers, data=body, auth=auth, verify=verify, proxies=proxies, timeout=timeout) + if not to_file: + return self.prepare_response(request) + + return self.return_file(request.text) + + def HEAD(self, url, headers="", body="", username="", password="", verify=True, http_proxy="", https_proxy="", timeout=5, to_file=False): + url = self.fix_url(url) + + parsed_headers = self.splitheaders(headers) + parsed_headers["User-Agent"] = "Shuffle Automation" + verify = self.checkverify(verify) + body = self.checkbody(body) + proxies = None + if http_proxy: + proxies["http"] = http_proxy + if https_proxy: + proxies["https"] = https_proxy + + auth=None + if username or password: + # Shouldn't be used if authorization headers exist + if "Authorization" in parsed_headers: + #print("Found authorization - skipping username & pw") + pass + else: + auth = requests.auth.HTTPBasicAuth(username, password) + + if not timeout: + timeout = 5 + if timeout: + timeout = int(timeout) + + if to_file == "true": + to_file = True + else: + to_file = False + + request = requests.head(url, headers=parsed_headers, auth=auth, verify=verify, proxies=proxies, timeout=timeout) + if not to_file: + return self.prepare_response(request) + + return self.return_file(request.text) + + def OPTIONS(self, url, headers="", body="", username="", password="", verify=True, http_proxy="", https_proxy="", timeout=5, to_file=False): + url = self.fix_url(url) + + parsed_headers = self.splitheaders(headers) + parsed_headers["User-Agent"] = "Shuffle Automation" + verify = self.checkverify(verify) + body = self.checkbody(body) + proxies = None + if http_proxy: + proxies["http"] = http_proxy + if https_proxy: + proxies["https"] = https_proxy + + auth=None + if username or password: + # Shouldn't be used if authorization headers exist + if "Authorization" in parsed_headers: + #print("Found authorization - skipping username & pw") + pass + else: + auth = requests.auth.HTTPBasicAuth(username, password) + + if not timeout: + timeout = 5 + + if timeout: + timeout = int(timeout) + + if to_file == "true": + to_file = True + else: + to_file = False + + request = requests.options(url, headers=parsed_headers, auth=auth, verify=verify, proxies=proxies, timeout=timeout) + if not to_file: + return self.prepare_response(request) + + return self.return_file(request.text) + + +# Run the actual thing after we've checked params +def run(request): + print("Starting cloud!") + action = request.get_json() + print(action) + print(type(action)) + authorization_key = action.get("authorization") + current_execution_id = action.get("execution_id") + + if action and "name" in action and "app_name" in action: + HTTP.run(action) + return f'Attempting to execute function {action["name"]} in app {action["app_name"]}' + else: + return f'Invalid action' + +if __name__ == "__main__": + HTTP.run() From 31555a08b07ed9f733b82a7d3fd390f31f1d696d Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 13 Nov 2023 14:57:55 +0100 Subject: [PATCH 063/259] Moved execute python function higher --- http/1.4.0/src/app.py | 12 ------------ shuffle-tools/1.2.0/api.yaml | 21 +++++++++++---------- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/http/1.4.0/src/app.py b/http/1.4.0/src/app.py index ab37aa24..73a661f8 100755 --- a/http/1.4.0/src/app.py +++ b/http/1.4.0/src/app.py @@ -45,18 +45,6 @@ def curl(self, statement): return item return item - #try: - # if not statement.startswith("curl "): - # statement = "curl %s" % statement - - # data = uncurl.parse(statement) - # request = eval(data) - # if isinstance(request, requests.models.Response): - # return request.text - # else: - # return "Unable to parse the curl parameter. Remember to start with curl " - #except: - # return "An error occurred during curl parsing" def splitheaders(self, headers): parsed_headers = {} diff --git a/shuffle-tools/1.2.0/api.yaml b/shuffle-tools/1.2.0/api.yaml index eb8311f8..96591918 100644 --- a/shuffle-tools/1.2.0/api.yaml +++ b/shuffle-tools/1.2.0/api.yaml @@ -25,6 +25,16 @@ actions: returns: schema: type: string + - name: execute_python + description: Runs python with the data input. Any prints will be returned. + parameters: + - name: code + description: The code to run. Can be a file ID from within Shuffle. + required: true + multiline: true + example: print("hello world") + schema: + type: string - name: check_cache_contains description: Checks Shuffle cache whether a user-provided key contains a value. Returns ALL the values previously appended. parameters: @@ -982,16 +992,7 @@ actions: example: '{"data": "Hello world"}' schema: type: string - - name: execute_python - description: Runs python with the data input. Any prints will be returned. - parameters: - - name: code - description: The code to run. Can be a file ID from within Shuffle. - required: true - multiline: true - example: print("hello world") - schema: - type: string + - name: run_math_operation description: Takes a math input and gives you the result parameters: From 3c1542610bc47ec1e9d5f22079da81359bc3671f Mon Sep 17 00:00:00 2001 From: Frikky Date: Tue, 14 Nov 2023 02:41:53 +0100 Subject: [PATCH 064/259] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3ed3879d..8d19233d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Shuffle Apps -This is a repository for apps to be used in [Shuffle](https://github.com/frikky/shuffle) +All public apps are available in the search, engine either in your local instance or on [https://shuffler.io/search?tab=apps](https://shuffler.io/search?tab=apps). This is a repository for apps to be used in [Shuffle](https://github.com/frikky/shuffle) -**PS:** These apps should be valid with WALKOFF, but the SDK is different, meaning you have to change the FIRST line in each Dockerfile (FROM frikky/shuffle:app_sdk). +**PS:** These apps should be valid with WALKOFF (from NSA), but the SDK is different, meaning you have to change the FIRST line in each Dockerfile (FROM frikky/shuffle:app_sdk) to make it compatible with Shuffle. ## App Creation App creation can be done with the Shuffle App Creator (exports as OpenAPI) or Python, which makes it possible to connect _literally_ any tool. Always prioritize using the App Creator when applicable. From 0a13133f494708ecb6c02c6394c4e6e1d1c10593 Mon Sep 17 00:00:00 2001 From: Frikky Date: Tue, 28 Nov 2023 01:40:38 +0100 Subject: [PATCH 065/259] Rebuild :3 --- shuffle-tools/1.2.0/api.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shuffle-tools/1.2.0/api.yaml b/shuffle-tools/1.2.0/api.yaml index 96591918..2d1d0787 100644 --- a/shuffle-tools/1.2.0/api.yaml +++ b/shuffle-tools/1.2.0/api.yaml @@ -1,7 +1,7 @@ --- app_version: 1.2.0 name: Shuffle Tools -description: A tool app for Shuffle. Gives access to most missing features along with Liquid. +description: A tool app for Shuffle. Gives access to most missing features along with Liquid. tags: - Testing - Shuffle From 44d6a83e1bc3fc0a679e9a7355618dbc64462fcc Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 29 Nov 2023 12:20:59 +0100 Subject: [PATCH 066/259] Autoparsing lists in shuffle tools parse list --- shuffle-tools/1.2.0/src/app.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index e24b8d46..e6d0d9d8 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -277,6 +277,15 @@ def parse(data): return return_value def parse_list(self, items, splitter="\n"): + # Check if it's already a list first + try: + newlist = json.loads(items) + if isinstance(newlist, list): + return newlist + + except Exception as e: + self.logger.info("[WARNING] Parse error - fallback: %s" % e) + if splitter == "": splitter = "\n" From 428d2e736483325f1841ab29d32b1b08da1c8b1e Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 30 Nov 2023 11:30:26 +0100 Subject: [PATCH 067/259] Rebuild with sdk --- shuffle-tools/1.2.0/api.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shuffle-tools/1.2.0/api.yaml b/shuffle-tools/1.2.0/api.yaml index 2d1d0787..169fc60d 100644 --- a/shuffle-tools/1.2.0/api.yaml +++ b/shuffle-tools/1.2.0/api.yaml @@ -1,7 +1,7 @@ --- app_version: 1.2.0 name: Shuffle Tools -description: A tool app for Shuffle. Gives access to most missing features along with Liquid. +description: A tool app for Shuffle. Gives access to most missing features along with Liquid. tags: - Testing - Shuffle From 8729052ad587440a45275ebc63880f3aaaeb9655 Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 6 Dec 2023 01:19:06 +0100 Subject: [PATCH 068/259] Create labeler.yml --- .github/labeler.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .github/labeler.yml diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 00000000..c53d2d66 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,2 @@ +App: + - '/.*/' From bd8f59297e83c1cc1dfbd5913eaac081e3fa0826 Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 6 Dec 2023 01:19:32 +0100 Subject: [PATCH 069/259] Update project_automation.yml --- .github/workflows/project_automation.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/project_automation.yml b/.github/workflows/project_automation.yml index 0e3bf945..837957f6 100644 --- a/.github/workflows/project_automation.yml +++ b/.github/workflows/project_automation.yml @@ -6,6 +6,16 @@ on: - opened jobs: + add-label: + name: Add label to issue + runs-on: ubuntu-latest + steps: + - uses: github/issue-labeler@v3.3 #May not be the latest version + with: + configuration-path: .github/labeler.yml + repo-token: ${{ secrets.ADD_TO_PROJECT_PAT }} + enable-versioned-regex: 0 + add-to-project: name: Add issue to project runs-on: ubuntu-latest From 8939e8e8b9533394e8b9211ba05f0157348b8e93 Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 7 Dec 2023 02:00:54 +0100 Subject: [PATCH 070/259] Rebuild with new sdk --- shuffle-tools/1.2.0/src/app.py | 1 - 1 file changed, 1 deletion(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index e6d0d9d8..ee515070 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -34,7 +34,6 @@ import struct import paramiko - import concurrent.futures import multiprocessing From 187222d1f1b2de10f3c2530976b6a60a0d5bb728 Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 7 Dec 2023 22:58:07 +0100 Subject: [PATCH 071/259] Rebuild with new SDK :) --- shuffle-tools/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/shuffle-tools/README.md b/shuffle-tools/README.md index 0a3518ac..febda00c 100644 --- a/shuffle-tools/README.md +++ b/shuffle-tools/README.md @@ -1,7 +1,6 @@ # Shuffle tools App documentation Shuffle tools is a utility app that simplifies your understanding of what happens in a node and also allows you to test on the fly. - ## Actions The Shuffle-tools app comes with a multitude of different actions, here we will check a few out and give a brief description. From 6f921bc50bb58fbc1a41fd5efa7b5a1174560400 Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 7 Dec 2023 23:42:44 +0100 Subject: [PATCH 072/259] App rebuild with new proxy --- shuffle-tools/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/shuffle-tools/README.md b/shuffle-tools/README.md index febda00c..0a3518ac 100644 --- a/shuffle-tools/README.md +++ b/shuffle-tools/README.md @@ -1,6 +1,7 @@ # Shuffle tools App documentation Shuffle tools is a utility app that simplifies your understanding of what happens in a node and also allows you to test on the fly. + ## Actions The Shuffle-tools app comes with a multitude of different actions, here we will check a few out and give a brief description. From c0cb179881da28c52dbed2758919494bc35098d3 Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 11 Dec 2023 13:54:32 +0100 Subject: [PATCH 073/259] Rebuild --- shuffle-tools/1.2.0/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/shuffle-tools/1.2.0/requirements.txt b/shuffle-tools/1.2.0/requirements.txt index 1dc466c6..6f2ad176 100644 --- a/shuffle-tools/1.2.0/requirements.txt +++ b/shuffle-tools/1.2.0/requirements.txt @@ -8,3 +8,4 @@ json2xml==3.6.0 ipaddress==1.0.23 google.auth==1.23.0 paramiko==3.1.0 + From ecda337f73f521f892180e416137bff8cdf18311 Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 11 Dec 2023 14:11:39 +0100 Subject: [PATCH 074/259] Removed linux/386 for now --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 53e0cbd7..aa536619 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -64,7 +64,7 @@ jobs: with: context: ${{ matrix.app }}/${{ matrix.version }} file: ${{ matrix.app }}/${{ matrix.version }}/Dockerfile - platforms: linux/amd64,linux/arm64,linux/386 + platforms: linux/amd64,linux/arm64 push: true tags: | ${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.app }}:${{ matrix.version }} From 05f83376920b3d1e1432d346e13c99ad04427ea6 Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 11 Dec 2023 15:10:40 +0100 Subject: [PATCH 075/259] Rebuild with new sdk --- shuffle-tools/1.2.0/api.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shuffle-tools/1.2.0/api.yaml b/shuffle-tools/1.2.0/api.yaml index 169fc60d..2d1d0787 100644 --- a/shuffle-tools/1.2.0/api.yaml +++ b/shuffle-tools/1.2.0/api.yaml @@ -1,7 +1,7 @@ --- app_version: 1.2.0 name: Shuffle Tools -description: A tool app for Shuffle. Gives access to most missing features along with Liquid. +description: A tool app for Shuffle. Gives access to most missing features along with Liquid. tags: - Testing - Shuffle From 1e4d4421f667680e1ed5fe25d85c6902dd085100 Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 18 Dec 2023 23:29:04 +0100 Subject: [PATCH 076/259] Added check_result to subflow to force it --- shuffle-subflow/1.1.0/src/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shuffle-subflow/1.1.0/src/app.py b/shuffle-subflow/1.1.0/src/app.py index a02ae79e..de0ce840 100644 --- a/shuffle-subflow/1.1.0/src/app.py +++ b/shuffle-subflow/1.1.0/src/app.py @@ -142,7 +142,7 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" return json.dumps(result) - def run_subflow(self, user_apikey, workflow, argument, source_workflow="", source_execution="", source_node="", source_auth="", startnode="", backend_url=""): + def run_subflow(self, user_apikey, workflow, argument, source_workflow="", source_execution="", source_node="", source_auth="", startnode="", backend_url="", check_result=""): #print("STARTNODE: %s" % startnode) url = "%s/api/v1/workflows/%s/execute" % (self.url, workflow) if len(self.base_url) > 0: From 3bef5a42cdbf5a7692b41d9e9a8e979cf9baa535 Mon Sep 17 00:00:00 2001 From: Frikky Date: Tue, 19 Dec 2023 10:52:08 +0100 Subject: [PATCH 077/259] Moved servicenow to unsupported as we got OpenAPI app for it --- servicenow/1.0.0/Dockerfile | 26 ---- servicenow/1.0.0/api.yaml | 146 --------------------- servicenow/1.0.0/requirements.txt | 1 - servicenow/1.0.0/src/app.py | 204 ------------------------------ 4 files changed, 377 deletions(-) delete mode 100644 servicenow/1.0.0/Dockerfile delete mode 100644 servicenow/1.0.0/api.yaml delete mode 100644 servicenow/1.0.0/requirements.txt delete mode 100755 servicenow/1.0.0/src/app.py diff --git a/servicenow/1.0.0/Dockerfile b/servicenow/1.0.0/Dockerfile deleted file mode 100644 index 364e1531..00000000 --- a/servicenow/1.0.0/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -# Base our app image off of the WALKOFF App SDK image -FROM frikky/shuffle:app_sdk as base - -# We're going to stage away all of the bloat from the build tools so lets create a builder stage -FROM base as builder - -# Install all alpine build tools needed for our pip installs -RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev - -# Install all of our pip packages in a single directory that we can copy to our base image later -RUN mkdir /install -WORKDIR /install -COPY requirements.txt /requirements.txt -RUN pip install --prefix="/install" -r /requirements.txt - -# Switch back to our base image and copy in all of our built packages and source code -FROM base -COPY --from=builder /install /usr/local -COPY src /app - -# Install any binary dependencies needed in our final image -# RUN apk --no-cache add --update my_binary_dependency - -# Finally, lets run our app! -WORKDIR /app -CMD python app.py --log-level DEBUG diff --git a/servicenow/1.0.0/api.yaml b/servicenow/1.0.0/api.yaml deleted file mode 100644 index 4fa62bef..00000000 --- a/servicenow/1.0.0/api.yaml +++ /dev/null @@ -1,146 +0,0 @@ -walkoff_version: 1.0.0 -app_version: 1.0.0 -name: servicenow -description: servicenow app -tags: - - tickets -categories: - - tickets -contact_info: - name: "@frikkylikeme" - url: https://github.com/frikky - email: "frikky@shuffler.io" -authentication: - required: true - parameters: - - name: url - description: The url your instance is at - multiline: false - example: "test.service-now.com" - required: true - schema: - type: string - - name: username - description: The user to authenticate with - multiline: false - example: "username12345" - required: true - schema: - type: string - - name: password - description: The password for the user to authenticate with - multiline: false - example: "pw1234" - required: true - schema: - type: string -actions: - - name: get_ticket - description: Get ticket ids - parameters: - - name: table_name - description: The type to get. Empty as default - multiline: false - example: "incident" - required: true - schema: - type: string - - name: sys_id - description: The ID to get from the table - multiline: false - example: "INC123456" - required: true - schema: - type: string - - name: number - description: The number to get instead of record_id - multiline: false - example: "20" - required: false - schema: - type: string - returns: - schema: - type: string - - name: create_ticket - description: Create a ticket - parameters: - - name: table_name - description: The table to create the ticket in - multiline: false - example: "incident" - required: true - schema: - type: string - - name: body - description: The body of the ticket - multiline: true - example: "{'short_description':'Unable to connect to office wifi','assignment_group':'287ebd7da9fe198100f92cc8d1d2154e','urgency':'2','impact':'2'}" - required: true - schema: - type: string - - name: file_id - description: Optional file to attach - multiline: false - example: "ca0c88a6-626e-4235-896f-ca18c96fd48e" - required: false - schema: - type: string - returns: - schema: - type: string - - name: update_ticket - description: Update a ticket - parameters: - - name: table_name - description: The table to create the ticket in - multiline: false - example: "incident" - required: true - schema: - type: string - - name: sys_id - description: The ticket to edit - multiline: false - example: "incident" - required: true - schema: - type: string - - name: body - description: JSON data of the data to replace - multiline: true - example: "{'short_description':'Unable to connect to office wifi','assignment_group':'287ebd7da9fe198100f92cc8d1d2154e','urgency':'2','impact':'2'}" - required: true - schema: - type: string - - name: file_id - description: Optional file to attach - multiline: false - example: "ca0c88a6-626e-4235-896f-ca18c96fd48e" - required: false - schema: - type: string - returns: - schema: - type: string - - name: list_table - description: Get ticket ids - parameters: - - name: table_name - description: The type to get. Empty as default - multiline: false - example: "incident" - required: true - schema: - type: string - - name: limit - description: The limit of items to get - multiline: false - example: "1" - required: false - schema: - type: string - returns: - schema: - type: string -large_image: data:image/jpg;base64,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 diff --git a/servicenow/1.0.0/requirements.txt b/servicenow/1.0.0/requirements.txt deleted file mode 100644 index fd7d3e06..00000000 --- a/servicenow/1.0.0/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -requests==2.25.1 \ No newline at end of file diff --git a/servicenow/1.0.0/src/app.py b/servicenow/1.0.0/src/app.py deleted file mode 100755 index d85dc832..00000000 --- a/servicenow/1.0.0/src/app.py +++ /dev/null @@ -1,204 +0,0 @@ -import time -import json -import random -import socket -import asyncio -import requests - -from walkoff_app_sdk.app_base import AppBase - -class Servicenow(AppBase): - __version__ = "1.0.0" - app_name = "servicenow" - - def __init__(self, redis, logger, console_logger=None): - """ - Each app should have this __init__ to set up Redis and logging. - :param redis: - :param logger: - :param console_logger: - """ - super().__init__(redis, logger, console_logger) - - def send_request(self, url, username, password, path, method='get', body=None, params=None, headers=None, json=None, files=None): - body = body if body is not None else {} - params = params if params is not None else {} - - url = '{}{}'.format(url, path) - print("HEADERS: %s" % headers) - if not headers and files == None: - headers = { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - } - - if files: - # Not supported in v2 - url = url.replace('v2', 'v1') - #{'file': ('report.csv', 'some,data,to,send\nanother,row,to,send\n')} - #file_entry = file['id'] - #file_name = file['name'] - try: - #shutil.copy(demisto.getFilePath(file_entry)['path'], file_name) - #with open(file_name, 'rb') as f: - #files = {'file': f} - - try: - res = requests.request(method, url, headers=headers, params=params, data=body, files=files, json=json, auth=(username, password)) - except requests.exceptions.ReadTimeout as e: - return "Readtimeout: %s" % e - except requests.exceptions.ConnectionError as e: - return "ConnectionError: %s" % e - - #shutil.rmtree(demisto.getFilePath(file_entry)['name'], ignore_errors=True) - except Exception as e: - return 'Failed to upload file - ' + str(e) - else: - try: - res = requests.request(method, url, headers=headers, data=json.dumps(body) if body else {}, json=json, params=params, auth=(username, password)) - except requests.exceptions.ReadTimeout as e: - return "Readtimeout: %s" % e - except requests.exceptions.ConnectionError as e: - return "ConnectionError: %s" % e - - try: - obj = res.json() - except Exception as e: - if not res.content: - return '' - return 'Error parsing reply - {} - {}'.format(res.content, str(e)) - - if 'error' in obj: - message = obj.get('error', {}).get('message') - details = obj.get('error', {}).get('detail') - if message == 'No Record found': - return { - # Return an empty results array - 'result': [] - } - return 'ServiceNow Error: {}, details: {}'.format(message, details) - - if res.status_code < 200 or res.status_code >= 300: - return 'Got status code {} with url {} with body {} with headers {}'.format(str(res.status_code), url, str(res.content), str(res.headers)) - - #print("RES: %s" % res) - #print("TEXT: %s" % res.text) - return res.text - - def get_ticket(self, url, username, password, table_name, sys_id, number=None): - path = None - query_params = {} # type: Dict - if sys_id: - path = "/api/now/v1/table/%s/%s" % (table_name, sys_id) - elif number: - path = '/api/now/v1/table/%s' % table_name - query_params = { - 'number': number - } - else: - # Only in cases where the table is of type ticket - return 'servicenow-get-ticket requires either ticket ID or ticket number' - - print("PATH: %s" % path) - return self.send_request(url, username, password, path, 'get', params=query_params) - - def list_table(self, url, username, password, table_name, limit=1): - query_params = { - "sysparm_limit": limit, - } - - #path = '/table/%s' % table_name - path = "/api/now/v1/table/%s" % table_name - - return self.send_request(url, username, password, path, 'get', params=query_params) - - def create_ticket(self, url, username, password, table_name, body, file_id=""): - if not isinstance(body, list) and not isinstance(body, object) and not isinstance(body, dict): - try: - data = json.loads(body) - except json.decoder.JSONDecodeError as e: - return {"success": False, "reason": e} - else: - data = body - - - path = "/api/now/v1/table/%s" % table_name - query_params = {} - base_request = self.send_request(url, username, password, path, 'post', params=query_params, json=data) - - if file_id: - tmp_file = self.get_file(file_id) - files = {'file': (tmp_file["filename"], tmp_file["data"])} - - try: - parsed_return = json.loads(base_request) - except: - print("[INFO] Failed parsed_return loading") - return base_request - - ticket_id = parsed_return["result"]["sys_id"] - params = { - "file_name": tmp_file["filename"], - "table_name": table_name, - "table_sys_id": ticket_id, - } - - filepath = "/api/now/v1/attachment/file" - file_request = self.send_request(url, username, password, filepath, 'post', params=params, files=files, headers={}) - print(file_request) - - return base_request - - def update_ticket(self, url, username, password, table_name, sys_id, body, file_id=""): - if not isinstance(body, list) and not isinstance(body, object) and not isinstance(body, dict): - try: - data = json.loads(body) - except json.decoder.JSONDecodeError as e: - return {"success": False, "reason": e} - else: - data = body - - - path = "/api/now/v1/table/%s/%s" % (table_name, sys_id) - query_params = {} - base_request = self.send_request(url, username, password, path, 'patch', params=query_params, json=data) - - if file_id: - tmp_file = self.get_file(file_id) - files = {'file': (tmp_file["filename"], tmp_file["data"])} - - try: - parsed_return = json.loads(base_request) - except: - print("[INFO] Failed parsed_return loading") - return base_request - - ticket_id = parsed_return["result"]["sys_id"] - params = { - "file_name": tmp_file["filename"], - "table_name": table_name, - "table_sys_id": ticket_id, - } - - filepath = "/api/now/v1/attachment/file" - file_request = self.send_request(url, username, password, filepath, '', params=params, files=files, headers={}) - print(file_request) - - return base_request - -# Run the actual thing after we've checked params -def run(request): - action = request.get_json() - print(action) - print(type(action)) - authorization_key = action.get("authorization") - current_execution_id = action.get("execution_id") - - if action and "name" in action and "app_name" in action: - Servicenow.run(action) - return f'Attempting to execute function {action["name"]} in app {action["app_name"]}' - else: - return f'Invalid action' - -if __name__ == "__main__": - Servicenow.run() From 6a31a2dbd94f49753dd1f62fe2f92e68347080ed Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 8 Jan 2024 15:38:08 +0100 Subject: [PATCH 078/259] Removed SSL verification in the Subflow app in case backend is screwed up --- shuffle-subflow/1.1.0/src/app.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/shuffle-subflow/1.1.0/src/app.py b/shuffle-subflow/1.1.0/src/app.py index de0ce840..282e79e2 100644 --- a/shuffle-subflow/1.1.0/src/app.py +++ b/shuffle-subflow/1.1.0/src/app.py @@ -106,7 +106,7 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" print("Should run email with targets: %s", jsondata["targets"]) - ret = requests.post("%s/api/v1/functions/sendmail" % url, json=jsondata, headers=headers) + ret = requests.post("%s/api/v1/functions/sendmail" % url, json=jsondata, headers=headers, verify=False) if ret.status_code != 200: print("Failed sending email. Data: %s" % ret.text) result["email"] = False @@ -131,7 +131,7 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" print("Should send sms with targets: %s", jsondata["numbers"]) - ret = requests.post("%s/api/v1/functions/sendsms" % url, json=jsondata, headers=headers) + ret = requests.post("%s/api/v1/functions/sendsms" % url, json=jsondata, headers=headers, verify=False) if ret.status_code != 200: print("Failed sending email. Data: %s" % ret.text) result["sms"] = False @@ -190,7 +190,7 @@ def run_subflow(self, user_apikey, workflow, argument, source_workflow="", sourc } if len(str(argument)) == 0: - ret = requests.post(url, headers=headers, params=params) + ret = requests.post(url, headers=headers, params=params, verify=False) else: if not isinstance(argument, list) and not isinstance(argument, dict): try: @@ -200,14 +200,14 @@ def run_subflow(self, user_apikey, workflow, argument, source_workflow="", sourc #print(f"ARG: {argument}") try: - ret = requests.post(url, headers=headers, params=params, json=argument) + ret = requests.post(url, headers=headers, params=params, json=argument, verify=False) print(f"Successfully sent argument of length {len(str(argument))} as JSON") except: try: - ret = requests.post(url, headers=headers, json=argument, params=params) + ret = requests.post(url, headers=headers, json=argument, params=params, verify=False) print("Successfully sent as JSON (2)") except: - ret = requests.post(url, headers=headers, data=argument, params=params) + ret = requests.post(url, headers=headers, data=argument, params=params, verify=False) print("Successfully sent as data (3)") print("Status: %d" % ret.status_code) From 376550d264bdc14590104cc470478c580e8c04df Mon Sep 17 00:00:00 2001 From: Frikky Date: Tue, 16 Jan 2024 14:03:30 +0100 Subject: [PATCH 079/259] Fixed potential proxy problems for subflow --- shuffle-subflow/1.1.0/src/app.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/shuffle-subflow/1.1.0/src/app.py b/shuffle-subflow/1.1.0/src/app.py index 282e79e2..0ea05ba6 100644 --- a/shuffle-subflow/1.1.0/src/app.py +++ b/shuffle-subflow/1.1.0/src/app.py @@ -67,7 +67,8 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" print("Should change port to 3001.") if "appspot.com" in frontend_url: frontend_url = "https://shuffler.io" - + if "run.app" in frontend_url: + frontend_url = "https://shuffler.io" for item in subflows: # In case of URL being passed, and not just ID @@ -106,7 +107,7 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" print("Should run email with targets: %s", jsondata["targets"]) - ret = requests.post("%s/api/v1/functions/sendmail" % url, json=jsondata, headers=headers, verify=False) + ret = requests.post("%s/api/v1/functions/sendmail" % url, json=jsondata, headers=headers, verify=False, proxies=self.proxy_config) if ret.status_code != 200: print("Failed sending email. Data: %s" % ret.text) result["email"] = False @@ -131,7 +132,7 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" print("Should send sms with targets: %s", jsondata["numbers"]) - ret = requests.post("%s/api/v1/functions/sendsms" % url, json=jsondata, headers=headers, verify=False) + ret = requests.post("%s/api/v1/functions/sendsms" % url, json=jsondata, headers=headers, verify=False, proxies=self.proxy_config) if ret.status_code != 200: print("Failed sending email. Data: %s" % ret.text) result["sms"] = False @@ -190,7 +191,7 @@ def run_subflow(self, user_apikey, workflow, argument, source_workflow="", sourc } if len(str(argument)) == 0: - ret = requests.post(url, headers=headers, params=params, verify=False) + ret = requests.post(url, headers=headers, params=params, verify=False, proxies=self.proxy_config) else: if not isinstance(argument, list) and not isinstance(argument, dict): try: @@ -200,14 +201,14 @@ def run_subflow(self, user_apikey, workflow, argument, source_workflow="", sourc #print(f"ARG: {argument}") try: - ret = requests.post(url, headers=headers, params=params, json=argument, verify=False) + ret = requests.post(url, headers=headers, params=params, json=argument, verify=False, proxies=self.proxy_config) print(f"Successfully sent argument of length {len(str(argument))} as JSON") except: try: - ret = requests.post(url, headers=headers, json=argument, params=params, verify=False) + ret = requests.post(url, headers=headers, json=argument, params=params, verify=False, proxies=self.proxy_config) print("Successfully sent as JSON (2)") except: - ret = requests.post(url, headers=headers, data=argument, params=params, verify=False) + ret = requests.post(url, headers=headers, data=argument, params=params, verify=False, proxies=self.proxy_config) print("Successfully sent as data (3)") print("Status: %d" % ret.status_code) From 37ac60d6d17311b89268beddb1154583845edabe Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 17 Jan 2024 17:48:21 +0100 Subject: [PATCH 080/259] Fixed verify=False by default for shuffle tools actions --- shuffle-tools/1.2.0/src/app.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index ee515070..86a88029 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -169,7 +169,7 @@ def send_sms_shuffle(self, apikey, phone_numbers, body): url = "https://shuffler.io/api/v1/functions/sendsms" headers = {"Authorization": "Bearer %s" % apikey} - return requests.post(url, headers=headers, json=data).text + return requests.post(url, headers=headers, json=data, verify=False).text # This is an email function of Shuffle def send_email_shuffle(self, apikey, recipients, subject, body, attachments=""): @@ -205,7 +205,7 @@ def send_email_shuffle(self, apikey, recipients, subject, body, attachments=""): url = "https://shuffler.io/api/v1/functions/sendmail" headers = {"Authorization": "Bearer %s" % apikey} - return requests.post(url, headers=headers, json=data).text + return requests.post(url, headers=headers, json=data, verify=False).text def repeat_back_to_me(self, call): return call @@ -906,6 +906,7 @@ def get_file_meta(self, file_id): "%s/api/v1/files/%s?execution_id=%s" % (self.url, file_id, self.current_execution_id), headers=headers, + verify=False, ) self.logger.info(f"RET: {ret}") @@ -922,6 +923,7 @@ def delete_file(self, file_id): "%s/api/v1/files/%s?execution_id=%s" % (self.url, file_id, self.current_execution_id), headers=headers, + verify=False, ) return ret.text @@ -984,7 +986,7 @@ def get_file_value(self, filedata): } def download_remote_file(self, url, custom_filename=""): - ret = requests.get(url, verify=False) # nosec + ret = requests.get(url, verify=False, verify=False) # nosec filename = url.split("/")[-1] if "?" in filename: filename = filename.split("?")[0] @@ -1699,7 +1701,7 @@ def check_cache_contains(self, key, value, append): else: append = False - get_response = requests.post(url, json=data) + get_response = requests.post(url, json=data, verify=False) try: allvalues = get_response.json() try: @@ -1714,7 +1716,7 @@ def check_cache_contains(self, key, value, append): data["value"] = json.dumps(new_value) set_url = "%s/api/v1/orgs/%s/set_cache" % (self.url, org_id) - set_response = requests.post(set_url, json=data) + set_response = requests.post(set_url, json=data, verify=False) try: allvalues = set_response.json() #allvalues["key"] = key @@ -1806,7 +1808,7 @@ def check_cache_contains(self, key, value, append): #return allvalues set_url = "%s/api/v1/orgs/%s/set_cache" % (self.url, org_id) - response = requests.post(set_url, json=data) + response = requests.post(set_url, json=data, verify=False) exception = "" try: allvalues = response.json() @@ -1878,7 +1880,7 @@ def change_cache_subkey(self, key, subkey, value, overwrite): "value": value, } - response = requests.post(url, json=data) + response = requests.post(url, json=data, verify=False) try: allvalues = response.json() allvalues["key"] = key @@ -1912,7 +1914,7 @@ def get_cache_value(self, key): "key": key, } - value = requests.post(url, json=data) + value = requests.post(url, json=data, verify=False) try: allvalues = value.json() self.logger.info("VAL1: ", allvalues) @@ -1961,7 +1963,7 @@ def set_cache_value(self, key, value): "value": value, } - response = requests.post(url, json=data) + response = requests.post(url, json=data, verify=False) try: allvalues = response.json() allvalues["key"] = key @@ -2088,7 +2090,7 @@ def run_oauth_request(self, url, jwt): data = "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=%s" % jwt - return requests.post(url, data=data, headers=headers).text + return requests.post(url, data=data, headers=headers, verify=False).text # Based on https://google-auth.readthedocs.io/en/master/reference/google.auth.crypt.html def get_jwt_from_file(self, file_id, jwt_audience, scopes, complete_request=True): From c92269ed9b2a250721cafcad7fae3b263ff6e990 Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 17 Jan 2024 17:51:40 +0100 Subject: [PATCH 081/259] Minor bug fix --- shuffle-tools/1.2.0/src/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 86a88029..9f4ded95 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -986,7 +986,7 @@ def get_file_value(self, filedata): } def download_remote_file(self, url, custom_filename=""): - ret = requests.get(url, verify=False, verify=False) # nosec + ret = requests.get(url, verify=False) # nosec filename = url.split("/")[-1] if "?" in filename: filename = filename.split("?")[0] From 6cd08c12009e7248ec85c354b297f2105e469957 Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 18 Jan 2024 14:22:35 +0100 Subject: [PATCH 082/259] Rebuild apps --- shuffle-tools/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/shuffle-tools/README.md b/shuffle-tools/README.md index 0a3518ac..febda00c 100644 --- a/shuffle-tools/README.md +++ b/shuffle-tools/README.md @@ -1,7 +1,6 @@ # Shuffle tools App documentation Shuffle tools is a utility app that simplifies your understanding of what happens in a node and also allows you to test on the fly. - ## Actions The Shuffle-tools app comes with a multitude of different actions, here we will check a few out and give a brief description. From 6c0b32222370df2d9edb3d0c9aae4b7c8afa5168 Mon Sep 17 00:00:00 2001 From: Frikky Date: Sun, 21 Jan 2024 23:45:25 +0100 Subject: [PATCH 083/259] Added more subflow fixes for user input --- shuffle-subflow/1.1.0/src/app.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/shuffle-subflow/1.1.0/src/app.py b/shuffle-subflow/1.1.0/src/app.py index 0ea05ba6..5c4b4d8a 100644 --- a/shuffle-subflow/1.1.0/src/app.py +++ b/shuffle-subflow/1.1.0/src/app.py @@ -51,11 +51,11 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" url = backend_url print("Found backend url: %s" % url) - print("AUTH: %s" % self.full_execution["authorization"]) + #print("AUTH: %s" % self.full_execution["authorization"]) #if len(information): # print("Should run arg: %s", information) - if len(subflow): + if len(subflow) > 0: #print("Should run subflow: %s", subflow) # Missing startnode (user input trigger) @@ -80,10 +80,10 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" argument = json.dumps({ "information": information, "parent_workflow": self.full_execution["workflow"]["id"], - "frontend_continue": "%s/workflows/%s/run?authorization=%s&reference_execution=%s&answer=true" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"]), - "frontend_abort": "%s/workflows/%s/run?authorization=%s&reference_execution=%s&answer=false" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"]), - "api_continue": "%s/api/v1/workflows/%s/execute?authorization=%s&reference_execution=%s&answer=true" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"]), - "api_abort": "%s/api/v1/workflows/%s/execute?authorization=%s&reference_execution=%s&answer=false" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"]), + "frontend_continue": "%s/workflows/%s/run?authorization=%s&reference_execution=%s&answer=true&source_node=%s" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"], source_node), + "frontend_abort": "%s/workflows/%s/run?authorization=%s&reference_execution=%s&answer=false&source_node=%s" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"], source_node), + "api_continue": "%s/api/v1/workflows/%s/execute?authorization=%s&reference_execution=%s&answer=true&source_node=%s" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"], source_node), + "api_abort": "%s/api/v1/workflows/%s/execute?authorization=%s&reference_execution=%s&answer=false&source_node=%s" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"], source_node), }) ret = self.run_subflow(user_apikey, item, argument, source_workflow=self.full_execution["workflow"]["id"], source_execution=self.full_execution["execution_id"], source_auth=self.full_execution["authorization"], startnode=startnode, backend_url=backend_url, source_node=source_node) From e1c1da12550c602e6b079a6939a4712f2b205f0b Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 25 Jan 2024 13:03:43 +0100 Subject: [PATCH 084/259] Running README --- shuffle-tools/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/shuffle-tools/README.md b/shuffle-tools/README.md index febda00c..0a3518ac 100644 --- a/shuffle-tools/README.md +++ b/shuffle-tools/README.md @@ -1,6 +1,7 @@ # Shuffle tools App documentation Shuffle tools is a utility app that simplifies your understanding of what happens in a node and also allows you to test on the fly. + ## Actions The Shuffle-tools app comes with a multitude of different actions, here we will check a few out and give a brief description. From a7b176a0b14658d4e7e79761084ce8bfde50f26c Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 26 Jan 2024 10:56:53 +0100 Subject: [PATCH 085/259] Added automatic length casting to larger than to avoid unecessary liquid --- shuffle-tools/1.2.0/src/app.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 9f4ded95..68dd5b8f 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -756,9 +756,30 @@ def filter_list(self, input_list, field, check, value, opposite): # CONTAINS FIND FOR LIST AND IN FOR STR elif check == "larger than": - if int(tmp) > int(value): - new_list.append(item) - else: + list_set = False + try: + if str(tmp).isdigit() and str(value).isdigit(): + if int(tmp) > int(value): + new_list.append(item) + list_set = True + except AttributeError as e: + self.logger.info("FAILED CHECKING LARGER THAN: %s" % e) + pass + + try: + value = len(json.loads(value)) + except Exception as e: + self.logger.info(f"[WARNING] Failed to convert destination to list: {e}") + + try: + # Check if it's a list in autocast and if so, check the length + if len(json.loads(tmp)) > int(value): + new_list.append(item) + list_set = True + except Exception as e: + self.logger.info(f"[WARNING] Failed to check if larger than as list: {e}") + + if not list_set: failed_list.append(item) elif check == "less than": if int(tmp) < int(value): From 692d7ef8a09db06a4167075c1ea5cc121d31ba95 Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 26 Jan 2024 11:42:34 +0100 Subject: [PATCH 086/259] Fixed less than function to not requiore liquid in filter --- shuffle-tools/1.2.0/src/app.py | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 68dd5b8f..80f3d157 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -782,9 +782,36 @@ def filter_list(self, input_list, field, check, value, opposite): if not list_set: failed_list.append(item) elif check == "less than": - if int(tmp) < int(value): - new_list.append(item) - else: + # Old + #if int(tmp) < int(value): + # new_list.append(item) + #else: + # failed_list.append(item) + + list_set = False + try: + if str(tmp).isdigit() and str(value).isdigit(): + if int(tmp) < int(value): + new_list.append(item) + list_set = True + except AttributeError as e: + self.logger.info("FAILED CHECKING LARGER THAN: %s" % e) + pass + + try: + value = len(json.loads(value)) + except Exception as e: + self.logger.info(f"[WARNING] Failed to convert destination to list: {e}") + + try: + # Check if it's a list in autocast and if so, check the length + if len(json.loads(tmp)) < int(value): + new_list.append(item) + list_set = True + except Exception as e: + self.logger.info(f"[WARNING] Failed to check if larger than as list: {e}") + + if not list_set: failed_list.append(item) elif check == "in cache key": From 8b7401f10481d179b9d4f0fc5061ec3a86f67f44 Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 26 Jan 2024 14:30:36 +0100 Subject: [PATCH 087/259] Filter list no longer NEEDS a value --- shuffle-tools/1.2.0/api.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shuffle-tools/1.2.0/api.yaml b/shuffle-tools/1.2.0/api.yaml index 2d1d0787..2f02a8f3 100644 --- a/shuffle-tools/1.2.0/api.yaml +++ b/shuffle-tools/1.2.0/api.yaml @@ -214,8 +214,8 @@ actions: schema: type: string - name: value - description: The value to check with - required: true + description: The value to compare with + required: false multiline: false example: "1.2.3.4" schema: From 391fd474d27a57a98fd4487f802444f42963ec1e Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 26 Jan 2024 17:14:25 +0100 Subject: [PATCH 088/259] Added verbosity to the check_cache_contains errors --- shuffle-tools/1.2.0/src/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 80f3d157..d321d42d 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -1887,6 +1887,7 @@ def check_cache_contains(self, key, value, append): #return allvalues except Exception as e: + print("[ERROR] Failed to get cache: %s" % e) return { "success": False, "key": key, From bacd0a815943cb89f18ba75cff049c6db9aa4615 Mon Sep 17 00:00:00 2001 From: Frikky Date: Sun, 28 Jan 2024 03:54:33 +0100 Subject: [PATCH 089/259] Added a concurrency file to test out multiprocessing of IOCs. No obvious improvement with it due to GIL. Removing IPv6 by default = 26% improvement~. Should optimize usage based on user ask --- shuffle-tools/1.2.0/api.yaml | 20 ++ shuffle-tools/1.2.0/src/app.py | 275 ++++++++++++------------- shuffle-tools/1.2.0/src/concurrency.py | 201 ++++++++++++++++++ 3 files changed, 356 insertions(+), 140 deletions(-) create mode 100644 shuffle-tools/1.2.0/src/concurrency.py diff --git a/shuffle-tools/1.2.0/api.yaml b/shuffle-tools/1.2.0/api.yaml index 2f02a8f3..57e918d1 100644 --- a/shuffle-tools/1.2.0/api.yaml +++ b/shuffle-tools/1.2.0/api.yaml @@ -1126,6 +1126,26 @@ actions: example: 'ls -la' schema: type: string + - name: parse_ioc_new + description: Parse IOC's based on https://github.com/fhightower/ioc-finder + parameters: + - name: input_string + description: The string to check + required: true + multiline: true + example: "123ijq192.168.3.6kljqwiejs8 https://shuffler.io" + schema: + type: string + - name: input_type + description: The string to check + required: false + multiline: false + example: "md5s" + schema: + type: string + returns: + schema: + type: string large_image: data:image/png;base64,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 # yamllint disable-line rule:line-length diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index d321d42d..c7bb499d 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -2446,118 +2446,21 @@ def run_ssh_command(self, host, port, user_name, private_key_file_id, password, return {"success":"false","message":str(e)} return {"success":"true","output": stdout.read().decode(errors='ignore')} - - def split_list(self, arr, length): - - num_arrays = len(arr) // length - remainder = len(arr) % length - - arrays = [] - start = 0 - - for _ in range(num_arrays): - sub_array = arr[start:start+length] - arrays.append(sub_array) - start += length - if remainder > 0: - sub_array = arr[start:] - arrays.append(sub_array) - - return arrays - - def split_text(self,text): - lines = text.split("\n") - num = 10 - new_text = "" - new_text_list = [] - for line in lines: - num -= 1 - new_text += line + "\n" - if num == 0: - new_text_list.append(new_text.strip("\n")) - num = 10 - new_text = "" - return new_text_list - - def _with_concurency(self,txt_data, ioc_types): - - cpu_count = multiprocessing.cpu_count() - #print("cpu count:",cpu_count) - start = time.perf_counter() - executor = concurrent.futures.ProcessPoolExecutor(cpu_count) - futures = [executor.submit(find_iocs, row, included_ioc_types=ioc_types) for row in txt_data] - results = [future.result() for future in futures] - #print("Total time taken:",time.perf_counter()-start) - return self._format_result(results) - - def _format_result(self,result): - final_result = {} - - for res in result: - for key,val in res.items(): - if key in final_result: - if isinstance(val, list) and len(val) > 0: - for i in val: - final_result[key].append(i) - elif isinstance(val, dict): - #print(key,":::",val) - if key in final_result: - if isinstance(val, dict): - for k,v in val.items(): - #print("k:",k,"v:",v) - val[k].append(v) - #print(val) - #final_result[key].append([i for i in val if len(val) > 0]) - else: - final_result[key] = val - - return final_result - - def quick_parse_ioc(self, file_id, input_type=None): - file_data = self.get_file(file_id) - - DEFAULT_IOC_TYPES = [ - "cves", - "domains", - "ipv4s", - "md5s", - "sha1s", - "sha256s", - "urls" - "email_addresses", - ] + def parse_ioc(self, input_string, input_type="all"): + ioc_types = ["domains", "urls", "email_addresses", "ipv4s", "ipv4_cidrs", "md5s", "sha256s", "sha1s", "cves"] - if input_type == "": - input_type = DEFAULT_IOC_TYPES + # Remember overriding ioc types we care about + if input_type == "" or input_type == "all": + input_type = "all" else: input_type = input_type.split(",") - - if file_data.get("data"): - file_data = file_data["data"].decode() - else: - return {"success":"false","message":"file not found"} + for item in input_type: + item = item.strip() - file_data = self.split_text(file_data) - res = self._with_concurency(file_data, ioc_types=input_type) - return res + ioc_types = input_type - def parse_ioc_new(self, input_string, input_type="all"): - ioc_types = ["domains", "urls", "email_addresses", "ipv6s", "ipv4s", "ipv4_cidrs", "md5s", "sha256s", "sha1s", "cves"] - input_string = str(input_string) - #if input_type == "": - # input_type = "all" - #else: - # input_type = input_type.split(",") - # for item in input_type: - # item = item.strip() - - # ioc_types = input_type - - file_data = self.split_text(input_string) - iocs = self._with_concurency(file_data, ioc_types=input_type) - #return res - #iocs = find_iocs(input_string, included_ioc_types=ioc_types) + iocs = find_iocs(input_string, included_ioc_types=ioc_types) newarray = [] for key, value in iocs.items(): if input_type != "all": @@ -2598,19 +2501,95 @@ def parse_ioc_new(self, input_string, input_type="all"): return "Failed to parse IOC's: %s" % e return newarray + - def parse_ioc(self, input_string, input_type="all"): - #if len(input_string) > 2500000 and (input_type == "" or input_type == "all"): - # return { - # "success": False, - # "reason": "Data too large (%d). Please reduce it below 2.5 Megabytes to use this action or specify the input type" % len(input_string) - # } + def split_text(self, text): + # Split text into chunks of 10kb. Add each 10k to array + # In case e.g. 1.2.3.4 lands exactly on 20k boundary, it may be useful to overlap here. + # (just shitty code to reduce chance of issues) while still going fast + arr_one = [] + max_len = 5000 + current_string = "" + overlaps = 100 + + for i in range(0, len(text)): + current_string += text[i] + if len(current_string) > max_len: + # Appending just in case even with overlaps + if len(text) > i+overlaps: + current_string += text[i+1:i+overlaps] + else: + current_string += text[i+1:] - # https://github.com/fhightower/ioc-finder/blob/6ff92a73a60e9233bf09b530ccafae4b4415b08a/ioc_finder/ioc_finder.py#L433 - ioc_types = ["domains", "urls", "email_addresses", "ipv6s", "ipv4s", "ipv4_cidrs", "md5s", "sha256s", "sha1s", "cves"] - input_string = str(input_string) + arr_one.append(current_string) + current_string = "" + + if len(current_string) > 0: + arr_one.append(current_string) + + return arr_one + + def _format_result(self, result): + final_result = {} + + for res in result: + for key,val in res.items(): + if key in final_result: + if isinstance(val, list) and len(val) > 0: + for i in val: + final_result[key].append(i) + elif isinstance(val, dict): + #print(key,":::",val) + if key in final_result: + if isinstance(val, dict): + for k,v in val.items(): + #print("k:",k,"v:",v) + val[k].append(v) + #print(val) + #final_result[key].append([i for i in val if len(val) > 0]) + else: + final_result[key] = val + + return final_result + + # See function for how it works~: parse_ioc_new(..) + def _with_concurency(self, array_of_strings, ioc_types): + results = [] + #start = time.perf_counter() + + # Workers dont matter..? + # What can we use instead? + print("Strings:", len(array_of_strings)) + + workers = 4 + with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as executor: + # Submit the find_iocs function for each string in the array + futures = [executor.submit( + find_iocs, + text=string, + included_ioc_types=ioc_types, + ) for string in array_of_strings] + + # Wait for all tasks to complete + concurrent.futures.wait(futures) + + # Retrieve the results if needed + results = [future.result() for future in futures] + + #print("Total time taken:", time.perf_counter()-start) + return self._format_result(results) + + # FIXME: Make this good and actually faster than normal + # For now: Concurrency doesn't make it faster due to GIL in python. + # May need to offload this to an executable or something + def parse_ioc_new(self, input_string, input_type="all"): if input_type == "": input_type = "all" + + ioc_types = ["domains", "urls", "email_addresses", "ipv4s", "ipv4_cidrs", "md5s", "sha256s", "sha1s", "cves"] + + if input_type == "" or input_type == "all": + ioc_types = ioc_types else: input_type = input_type.split(",") for item in input_type: @@ -2618,40 +2597,55 @@ def parse_ioc(self, input_string, input_type="all"): ioc_types = input_type - iocs = find_iocs(input_string, included_ioc_types=ioc_types) + input_string = str(input_string) + + if len(input_string) > 10000: + iocs = self._with_concurency(self.split_text(input_string), ioc_types=ioc_types) + else: + iocs = find_iocs(input_string, included_ioc_types=ioc_types) + newarray = [] for key, value in iocs.items(): if input_type != "all": if key not in input_type: continue + + if len(value) == 0: + continue - if len(value) > 0: - for item in value: - # If in here: attack techniques. Shouldn't be 3 levels so no - # recursion necessary - if isinstance(value, dict): - for subkey, subvalue in value.items(): - if len(subvalue) > 0: - for subitem in subvalue: - data = { - "data": subitem, - "data_type": "%s_%s" % (key[:-1], subkey), - } - if data not in newarray: - newarray.append(data) - else: - data = {"data": item, "data_type": key[:-1]} - if data not in newarray: - newarray.append(data) + for item in value: + # If in here: attack techniques. Shouldn't be 3 levels so no + # recursion necessary + if isinstance(value, dict): + for subkey, subvalue in value.items(): + if len(subvalue) == 0: + continue + + for subitem in subvalue: + data = { + "data": subitem, + "data_type": "%s_%s" % (key[:-1], subkey), + } + + if data not in newarray: + newarray.append(data) + else: + data = {"data": item, "data_type": key[:-1]} + if data not in newarray: + newarray.append(data) # Reformatting IP + i = -1 for item in newarray: - if "ip" in item["data_type"]: - item["data_type"] = "ip" - try: - item["is_private_ip"] = ipaddress.ip_address(item["data"]).is_private - except: - self.logger.info("Error parsing %s" % item["data"]) + i += 1 + if "ip" not in item["data_type"]: + continue + + newarray[i]["data_type"] = "ip" + try: + newarray[i]["is_private_ip"] = ipaddress.ip_address(item["data"]).is_private + except Exception as e: + print("Error parsing %s: %s" % (item["data"], e)) try: newarray = json.dumps(newarray) @@ -2659,6 +2653,7 @@ def parse_ioc(self, input_string, input_type="all"): return "Failed to parse IOC's: %s" % e return newarray + if __name__ == "__main__": Tools.run() diff --git a/shuffle-tools/1.2.0/src/concurrency.py b/shuffle-tools/1.2.0/src/concurrency.py new file mode 100644 index 00000000..420d1686 --- /dev/null +++ b/shuffle-tools/1.2.0/src/concurrency.py @@ -0,0 +1,201 @@ +import time +import json +import ipaddress +import concurrent.futures +from functools import partial +from ioc_finder import find_iocs + +class Test(): + def split_text(self, text): + # Split text into chunks of 10kb. Add each 10k to array + # In case e.g. 1.2.3.4 lands exactly on 20k boundary, it may be useful to overlap here. + # (just shitty code to reduce chance of issues) while still going fast + + arr_one = [] + max_len = 2500 + current_string = "" + overlaps = 100 + + + for i in range(0, len(text)): + current_string += text[i] + if len(current_string) > max_len: + # Appending just in case even with overlaps + if len(text) > i+overlaps: + current_string += text[i+1:i+overlaps] + else: + current_string += text[i+1:] + + arr_one.append(current_string) + current_string = "" + + if len(current_string) > 0: + arr_one.append(current_string) + + #print("DATA:", arr_one) + print("Strings:", len(arr_one)) + #exit() + + return arr_one + + def _format_result(self, result): + final_result = {} + + for res in result: + for key, val in res.items(): + if key in final_result: + if isinstance(val, list) and len(val) > 0: + for i in val: + final_result[key].append(i) + elif isinstance(val, dict): + #print(key,":::",val) + if key in final_result: + if isinstance(val, dict): + for k,v in val.items(): + #print("k:",k,"v:",v) + val[k].append(v) + #print(val) + #final_result[key].append([i for i in val if len(val) > 0]) + else: + final_result[key] = val + + return final_result + + def worker_function(self, inputdata): + return find_iocs(inputdata["data"], included_ioc_types=inputdata["ioc_types"]) + + def _with_concurency(self, array_of_strings, ioc_types): + results = [] + #start = time.perf_counter() + + # Workers dont matter..? + # What can we use instead? + + results = [] + workers = 4 + with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as executor: + # Submit the find_iocs function for each string in the array + futures = [executor.submit( + find_iocs, + text=string, + included_ioc_types=ioc_types, + ) for string in array_of_strings] + + # Wait for all tasks to complete + concurrent.futures.wait(futures) + + # Retrieve the results if needed + results = [future.result() for future in futures] + + return self._format_result(results) + + def parse_ioc_new(self, input_string, input_type="all"): + if input_type == "": + input_type = "all" + + #ioc_types = ["domains", "urls", "email_addresses", "ipv6s", "ipv4s", "ipv4_cidrs", "md5s", "sha256s", "sha1s", "cves"] + ioc_types = ["domains", "urls", "email_addresses", "ipv4s", "ipv4_cidrs", "md5s", "sha256s", "sha1s", "cves"] + + # urls = 10.4 -> 9.1 + # emails = 10.4 -> 9.48 + # ipv6s = 10.4 -> 7.37 + # ipv4 cidrs = 10.4 -> 10.44 + + if input_type == "" or input_type == "all": + ioc_types = ioc_types + else: + input_type = input_type.split(",") + for item in input_type: + item = item.strip() + + ioc_types = input_type + + input_string = str(input_string) + if len(input_string) > 10000: + iocs = self._with_concurency(self.split_text(input_string), ioc_types=ioc_types) + else: + iocs = find_iocs(input_string, included_ioc_types=ioc_types) + + newarray = [] + for key, value in iocs.items(): + if input_type != "all": + if key not in input_type: + continue + + if len(value) == 0: + continue + + for item in value: + # If in here: attack techniques. Shouldn't be 3 levels so no + # recursion necessary + if isinstance(value, dict): + for subkey, subvalue in value.items(): + if len(subvalue) == 0: + continue + + for subitem in subvalue: + data = { + "data": subitem, + "data_type": "%s_%s" % (key[:-1], subkey), + } + + if data not in newarray: + newarray.append(data) + else: + data = {"data": item, "data_type": key[:-1]} + if data not in newarray: + newarray.append(data) + + # Reformatting IP + i = -1 + for item in newarray: + i += 1 + if "ip" not in item["data_type"]: + continue + + newarray[i]["data_type"] = "ip" + try: + newarray[i]["is_private_ip"] = ipaddress.ip_address(item["data"]).is_private + except Exception as e: + print("Error parsing %s: %s" % (item["data"], e)) + + try: + newarray = json.dumps(newarray) + except json.decoder.JSONDecodeError as e: + return "Failed to parse IOC's: %s" % e + + return newarray + +# Make it not run this for multithreads +if __name__ == "__main__": + + input_string = "" + with open("testdata.txt", "r") as f: + input_string = f.read() + + try: + json_data = json.loads(input_string) + # If array, loop + if isinstance(json_data, list): + cnt = 0 + start = time.perf_counter() + for item in json_data: + cnt += 1 + classdata = Test() + + ret = classdata.parse_ioc_new(item) + #print("OUTPUT1: ", ret) + + #if cnt == 5: + # break + + print("Total time taken:", time.perf_counter()-start) + else: + classdata = Test() + ret = classdata.parse_ioc_new(input_string) + print("OUTPUT2: ", ret) + except Exception as e: + classdata = Test() + ret = classdata.parse_ioc_new(json_data) + print("OUTPUT3: ", ret) + From 49140e044fea5bb630bc3a88460edc7ff919a1a1 Mon Sep 17 00:00:00 2001 From: Frikky Date: Sun, 28 Jan 2024 03:57:10 +0100 Subject: [PATCH 090/259] Added more info about types that can be used --- shuffle-tools/1.2.0/api.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shuffle-tools/1.2.0/api.yaml b/shuffle-tools/1.2.0/api.yaml index 57e918d1..850844d7 100644 --- a/shuffle-tools/1.2.0/api.yaml +++ b/shuffle-tools/1.2.0/api.yaml @@ -268,7 +268,7 @@ actions: # schema: # type: string - name: parse_ioc - description: Parse IOC's based on https://github.com/fhightower/ioc-finder + description: Parse IOC's based on https://github.com/fhightower/ioc-finder. Specify input type to optimize speed: domains, urls, email_addresses, ipv4s, ipv4_cidrs, md5s, sha256s, sha1s, cves and more.. parameters: - name: input_string description: The string to check @@ -281,7 +281,7 @@ actions: description: The string to check required: false multiline: false - example: "md5s" + example: "domains,urls,email_addresses,ipv4s,ipv4_cidrs,ipv6s,md5s,sha256s,sha1s,cves schema: type: string returns: From f7d6d5a494a7803d997fe065594ce055631caa18 Mon Sep 17 00:00:00 2001 From: Frikky Date: Tue, 30 Jan 2024 16:10:08 +0100 Subject: [PATCH 091/259] Fixed some parse ioc & cache contains checks --- shuffle-tools/1.2.0/api.yaml | 44 +++++++++--------- shuffle-tools/1.2.0/src/app.py | 81 +++++++++++++++++----------------- 2 files changed, 63 insertions(+), 62 deletions(-) diff --git a/shuffle-tools/1.2.0/api.yaml b/shuffle-tools/1.2.0/api.yaml index 850844d7..cfa1a55b 100644 --- a/shuffle-tools/1.2.0/api.yaml +++ b/shuffle-tools/1.2.0/api.yaml @@ -268,7 +268,7 @@ actions: # schema: # type: string - name: parse_ioc - description: Parse IOC's based on https://github.com/fhightower/ioc-finder. Specify input type to optimize speed: domains, urls, email_addresses, ipv4s, ipv4_cidrs, md5s, sha256s, sha1s, cves and more.. + description: "Parse IOC's based on https://github.com/fhightower/ioc-finder. Specify input type to optimize speed: domains, urls, email_addresses, ipv4s, ipv4_cidrs, md5s, sha256s, sha1s, cves and more.." parameters: - name: input_string description: The string to check @@ -281,7 +281,7 @@ actions: description: The string to check required: false multiline: false - example: "domains,urls,email_addresses,ipv4s,ipv4_cidrs,ipv6s,md5s,sha256s,sha1s,cves + example: "domains,urls,email_addresses,ipv4s,ipv4_cidrs,ipv6s,md5s,sha256s,sha1s,cves" schema: type: string returns: @@ -1126,26 +1126,26 @@ actions: example: 'ls -la' schema: type: string - - name: parse_ioc_new - description: Parse IOC's based on https://github.com/fhightower/ioc-finder - parameters: - - name: input_string - description: The string to check - required: true - multiline: true - example: "123ijq192.168.3.6kljqwiejs8 https://shuffler.io" - schema: - type: string - - name: input_type - description: The string to check - required: false - multiline: false - example: "md5s" - schema: - type: string - returns: - schema: - type: string + #- name: parse_ioc_new + # description: Parse IOC's based on https://github.com/fhightower/ioc-finder + # parameters: + # - name: input_string + # description: The string to check + # required: true + # multiline: true + # example: "123ijq192.168.3.6kljqwiejs8 https://shuffler.io" + # schema: + # type: string + # - name: input_type + # description: The string to check + # required: false + # multiline: false + # example: "md5s" + # schema: + # type: string + # returns: + # schema: + # type: string large_image: data:image/png;base64,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 # yamllint disable-line rule:line-length diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index c7bb499d..d94f2bcf 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -1797,37 +1797,47 @@ def check_cache_contains(self, key, value, append): if allvalues["value"] == None or allvalues["value"] == "null": allvalues["value"] = "[]" + allvalues["value"] = str(allvalues["value"]) + try: parsedvalue = json.loads(allvalues["value"]) except json.decoder.JSONDecodeError as e: - parsedvalue = [] + parsedvalue = [str(allvalues["value"])] + except Exception as e: + print("Error parsing JSON - overriding: %s" % e) + parsedvalue = [str(allvalues["value"])] - #return parsedvalue + print("In ELSE2: '%s'" % parsedvalue) - for item in parsedvalue: - #return "%s %s" % (item, value) - if item == value: - if not append: - return { - "success": True, - "found": True, - "reason": "Found and not appending!", - "key": key, - "search": value, - "value": json.loads(allvalues["value"]), - } - else: - return { - "success": True, - "found": True, - "reason": "Found, was appending, but item already exists", - "key": key, - "search": value, - "value": json.loads(allvalues["value"]), - } - - # Lol - break + try: + for item in parsedvalue: + #return "%s %s" % (item, value) + if item == value: + if not append: + return { + "success": True, + "found": True, + "reason": "Found and not appending!", + "key": key, + "search": value, + "value": json.loads(allvalues["value"]), + } + else: + return { + "success": True, + "found": True, + "reason": "Found, was appending, but item already exists", + "key": key, + "search": value, + "value": json.loads(allvalues["value"]), + } + + # Lol + break + except Exception as e: + print("Error in check_cache_contains: %s" % e) + parsedvalue = [str(parsedvalue)] + append = True if not append: return { @@ -1839,21 +1849,12 @@ def check_cache_contains(self, key, value, append): "value": json.loads(allvalues["value"]), } - #parsedvalue = json.loads(allvalues["value"]) - #if parsedvalue == None: - # parsedvalue = [] - - #return parsedvalue new_value = parsedvalue if new_value == None: new_value = [value] new_value.append(value) - - #return new_value - data["value"] = json.dumps(new_value) - #return allvalues set_url = "%s/api/v1/orgs/%s/set_cache" % (self.url, org_id) response = requests.post(set_url, json=data, verify=False) @@ -1887,11 +1888,11 @@ def check_cache_contains(self, key, value, append): #return allvalues except Exception as e: - print("[ERROR] Failed to get cache: %s" % e) + print("[ERROR] Failed to handle cache contains: %s" % e) return { "success": False, "key": key, - "reason": f"Failed to get cache: {e}", + "reason": f"Failed to handle cache contains. Is the original value a list?: {e}", "search": value, "found": False, } @@ -1966,11 +1967,11 @@ def get_cache_value(self, key): value = requests.post(url, json=data, verify=False) try: allvalues = value.json() - self.logger.info("VAL1: ", allvalues) + #self.logger.info("VAL1: ", allvalues) allvalues["key"] = key - self.logger.info("VAL2: ", allvalues) + #self.logger.info("VAL2: ", allvalues) - if allvalues["success"] == True: + if allvalues["success"] == True and len(allvalues["value"]) > 0: allvalues["found"] = True else: allvalues["success"] = True From 5b9a2c01eb0a2babaf91f02459a80187dec83465 Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 1 Feb 2024 15:22:24 +0100 Subject: [PATCH 092/259] Minor http update --- http/1.4.0/src/app.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/http/1.4.0/src/app.py b/http/1.4.0/src/app.py index 73a661f8..ff2ec91b 100755 --- a/http/1.4.0/src/app.py +++ b/http/1.4.0/src/app.py @@ -154,14 +154,16 @@ def prepare_response(self, request): except: pass - return json.dumps({ - "success": True, + parseddata = { "status": request.status_code, + "body": jsondata, "url": request.url, "headers": parsedheaders, - "body": jsondata, "cookies":cookies, - }) + "success": True, + } + + return json.dumps(parseddata) except Exception as e: print(f"[WARNING] Failed in request: {e}") return request.text From 8060d87a8272ef9ab17e9986d81b3e2a5da59aca Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 2 Feb 2024 01:59:14 +0100 Subject: [PATCH 093/259] Force rebuild --- shuffle-tools/1.2.0/requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/shuffle-tools/1.2.0/requirements.txt b/shuffle-tools/1.2.0/requirements.txt index 6f2ad176..1dc466c6 100644 --- a/shuffle-tools/1.2.0/requirements.txt +++ b/shuffle-tools/1.2.0/requirements.txt @@ -8,4 +8,3 @@ json2xml==3.6.0 ipaddress==1.0.23 google.auth==1.23.0 paramiko==3.1.0 - From 2fb2483396b74d418c33fa1928691712370f4a56 Mon Sep 17 00:00:00 2001 From: Frikky Date: Tue, 6 Feb 2024 18:15:11 +0100 Subject: [PATCH 094/259] Parsed stuff as strings properly for ioc parser --- shuffle-tools/1.2.0/src/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index d94f2bcf..03019ed7 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -2461,7 +2461,7 @@ def parse_ioc(self, input_string, input_type="all"): ioc_types = input_type - iocs = find_iocs(input_string, included_ioc_types=ioc_types) + iocs = find_iocs(str(input_string), included_ioc_types=ioc_types) newarray = [] for key, value in iocs.items(): if input_type != "all": From cec44a98fd40f855f87742c05de5426a35ae1c28 Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 21 Feb 2024 10:43:02 +0100 Subject: [PATCH 095/259] Added an action for listing ips in a CIDR range --- shuffle-tools/1.2.0/api.yaml | 13 ++++++++++++ shuffle-tools/1.2.0/src/app.py | 38 ++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/shuffle-tools/1.2.0/api.yaml b/shuffle-tools/1.2.0/api.yaml index cfa1a55b..66704dd8 100644 --- a/shuffle-tools/1.2.0/api.yaml +++ b/shuffle-tools/1.2.0/api.yaml @@ -955,6 +955,19 @@ actions: returns: schema: type: string + - name: list_cidr_ips + description: Lists the IPs for a CIDR + parameters: + - name: cidr + description: IP CIDR to check + multiline: false + example: "1.1.1.0/24" + required: True + schema: + type: string + returns: + schema: + type: string - name: cidr_ip_match description: Check if an IP is contained in a CIDR defined network parameters: diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 03019ed7..2708309c 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -2654,6 +2654,44 @@ def parse_ioc_new(self, input_string, input_type="all"): return "Failed to parse IOC's: %s" % e return newarray + + def list_cidr_ips(self, cidr): + defaultreturn = { + "success": False, + "reason": "Invalid CIDR address" + } + + if not cidr: + return defaultreturn + + if "/" not in cidr: + defaultreturn["reason"] = "CIDR address must contain / (e.g. /12)" + return defaultreturn + + try: + cidrnumber = int(cidr.split("/")[1]) + except ValueError as e: + defaultreturn["exception"] = str(e) + return defaultreturn + + if cidrnumber < 12: + defaultreturn["reason"] = "CIDR address too large. Please stay above /12" + return defaultreturn + + try: + net = ipaddress.ip_network(cidr) + except ValueError as e: + defaultreturn["exception"] = str(e) + return defaultreturn + + ips = [str(ip) for ip in net] + returnvalue = { + "success": True, + "amount": len(ips), + "ips": ips + } + + return returnvalue if __name__ == "__main__": From 727466adb5a9492410394151aebc4cb79f36ed18 Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 28 Feb 2024 21:50:00 +0100 Subject: [PATCH 096/259] Removed verbosity from shuffle tools --- shuffle-tools/1.2.0/api.yaml | 2 +- shuffle-tools/1.2.0/src/app.py | 39 ++-------------------------------- 2 files changed, 3 insertions(+), 38 deletions(-) diff --git a/shuffle-tools/1.2.0/api.yaml b/shuffle-tools/1.2.0/api.yaml index 66704dd8..b67bc534 100644 --- a/shuffle-tools/1.2.0/api.yaml +++ b/shuffle-tools/1.2.0/api.yaml @@ -1,7 +1,7 @@ --- app_version: 1.2.0 name: Shuffle Tools -description: A tool app for Shuffle. Gives access to most missing features along with Liquid. +description: A tool app for Shuffle. Gives access to most missing features along with Liquid. tags: - Testing - Shuffle diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 2708309c..5b19f78d 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -684,8 +684,6 @@ def filter_list(self, input_list, field, check, value, opposite): new_list.append(item) found = True break - else: - print("Nothing matching") if not found: failed_list.append(item) @@ -1533,7 +1531,7 @@ def fix_json(self, json_data): del json_data[key] except Exception as e: - print("[DEBUG] Problem in JSON (fix_json): %s" % e) + pass return json_data @@ -1587,8 +1585,6 @@ def compare_relative_date( ): if timestamp== "None": return False - - print("Converting input date.") if date_format == "autodetect": input_dt = dateutil_parser(timestamp).replace(tzinfo=None) @@ -1632,12 +1628,7 @@ def compare_relative_date( comparison_dt = formatted_dt + delta #comparison_dt = datetime.datetime.utcnow() - print("{} {} {} is {}. Delta: {}".format(offset, units, direction, comparison_dt, delta)) - diff = int((input_dt - comparison_dt).total_seconds()) - print( - "\nDifference between {} and {} is {} seconds ({} days)\n".format(timestamp, comparison_dt, diff, int(diff/86400)) - ) if units == "seconds": diff = diff @@ -1675,19 +1666,6 @@ def compare_relative_date( if direction == "ahead" and diff != 0: result = not (result) - print( - "At {}, is {} {} to {} {} {}? {}. Diff {}".format( - formatted_dt, - timestamp, - equality_test, - offset, - units, - direction, - result, - diff, - ) - ) - parsed_string = "%s %s %s %s" % (equality_test, offset, units, direction) newdiff = diff if newdiff < 0: @@ -1804,11 +1782,8 @@ def check_cache_contains(self, key, value, append): except json.decoder.JSONDecodeError as e: parsedvalue = [str(allvalues["value"])] except Exception as e: - print("Error parsing JSON - overriding: %s" % e) parsedvalue = [str(allvalues["value"])] - print("In ELSE2: '%s'" % parsedvalue) - try: for item in parsedvalue: #return "%s %s" % (item, value) @@ -1835,7 +1810,6 @@ def check_cache_contains(self, key, value, append): # Lol break except Exception as e: - print("Error in check_cache_contains: %s" % e) parsedvalue = [str(parsedvalue)] append = True @@ -1888,7 +1862,6 @@ def check_cache_contains(self, key, value, append): #return allvalues except Exception as e: - print("[ERROR] Failed to handle cache contains: %s" % e) return { "success": False, "key": key, @@ -2197,7 +2170,6 @@ def get_jwt(sa_keyfile, #signer = crypt.RSASigner.from_service_account_file(sa_keyfile) signer = crypt.RSASigner.from_string(sa_keyfile) jwt_token = jwt.encode(signer, payload) - # print(jwt_token.decode('utf-8')) return jwt_token @@ -2435,7 +2407,6 @@ def run_ssh_command(self, host, port, user_name, private_key_file_id, password, except Exception as e: return {"success":"false","message":str(e)} else: - #print("AUTH WITH PASSWORD") try: ssh_client.connect(hostname=host,username=user_name,port=port, password=str(password)) except Exception as e: @@ -2540,14 +2511,10 @@ def _format_result(self, result): for i in val: final_result[key].append(i) elif isinstance(val, dict): - #print(key,":::",val) if key in final_result: if isinstance(val, dict): for k,v in val.items(): - #print("k:",k,"v:",v) val[k].append(v) - #print(val) - #final_result[key].append([i for i in val if len(val) > 0]) else: final_result[key] = val @@ -2560,7 +2527,6 @@ def _with_concurency(self, array_of_strings, ioc_types): # Workers dont matter..? # What can we use instead? - print("Strings:", len(array_of_strings)) workers = 4 with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as executor: @@ -2577,7 +2543,6 @@ def _with_concurency(self, array_of_strings, ioc_types): # Retrieve the results if needed results = [future.result() for future in futures] - #print("Total time taken:", time.perf_counter()-start) return self._format_result(results) # FIXME: Make this good and actually faster than normal @@ -2646,7 +2611,7 @@ def parse_ioc_new(self, input_string, input_type="all"): try: newarray[i]["is_private_ip"] = ipaddress.ip_address(item["data"]).is_private except Exception as e: - print("Error parsing %s: %s" % (item["data"], e)) + pass try: newarray = json.dumps(newarray) From 7bac3f164f7695f2e66389f60f07b5b7f2786ce8 Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 29 Feb 2024 04:25:09 +0100 Subject: [PATCH 097/259] Removed more verbosity for tools --- shuffle-tools/1.2.0/src/app.py | 97 +++++----------------------------- 1 file changed, 13 insertions(+), 84 deletions(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 5b19f78d..740edbd5 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -111,7 +111,6 @@ def base64_conversion(self, string, operation): except Exception as e: #return string.decode("utf-16") - self.logger.info(f"[WARNING] Error in normal decoding: {e}") return { "success": False, "reason": f"Error decoding the base64: {e}", @@ -121,7 +120,6 @@ def base64_conversion(self, string, operation): # if str(newvar).startswith("b'") and str(newvar).endswith("'"): # newvar = newvar[2:-1] #except Exception as e: - # self.logger.info(f"Encoding issue in base64: {e}") #return newvar #try: @@ -200,7 +198,7 @@ def send_email_shuffle(self, apikey, recipients, subject, body, attachments=""): data["attachments"] = files except Exception as e: - self.logger.info(f"Error in attachment parsing for email: {e}") + pass url = "https://shuffler.io/api/v1/functions/sendmail" @@ -303,7 +301,6 @@ def get_length(self, item): return str(len(item)) def set_json_key(self, json_object, key, value): - self.logger.info(f"OBJ: {json_object}\nKEY: {key}\nVAL: {value}") if isinstance(json_object, str): try: json_object = json.loads(json_object) @@ -350,7 +347,6 @@ def set_json_key(self, json_object, key, value): buildstring += f"[\"{subkey}\"]" buildstring += f" = {value}" - self.logger.info("BUILD: %s" % buildstring) #output = exec(buildstring) @@ -434,7 +430,6 @@ def regex_capture_group(self, input_data, regex): } matches = re.findall(regex, input_data) - self.logger.info(f"{matches}") found = False for item in matches: if isinstance(item, str): @@ -467,19 +462,12 @@ def regex_replace( self, input_data, regex, replace_string="", ignore_case="False" ): - #self.logger.info("=" * 80) - #self.logger.info(f"Regex: {regex}") - #self.logger.info(f"replace_string: {replace_string}") - #self.logger.info("=" * 80) - if ignore_case.lower().strip() == "true": return re.sub(regex, replace_string, input_data, flags=re.IGNORECASE) else: return re.sub(regex, replace_string, input_data) def execute_python(self, code): - self.logger.info(f"Python code {len(code)}. If uuid, we'll try to download and use the file.") - if len(code) == 36 and "-" in code: filedata = self.get_file(code) if filedata["success"] == False: @@ -523,7 +511,6 @@ def custom_print(*args, **kwargs): #try: # s = s.encode("utf-8") #except Exception as e: - # self.logger.info(f"Failed utf-8 encoding response: {e}") try: return { @@ -559,7 +546,6 @@ def execute_bash(self, code, shuffle_input): stdout = process.communicate() item = "" if len(stdout[0]) > 0: - self.logger.info("[DEBUG] Succesfully ran bash!") item = stdout[0] else: self.logger.info(f"[ERROR] FAILED to run bash command {code}!") @@ -588,7 +574,6 @@ def check_wildcard(self, wildcardstring, matching_string): return False def filter_list(self, input_list, field, check, value, opposite): - self.logger.info(f"\nRunning function with list {input_list}") # Remove hashtags on the fly # E.g. #.fieldname or .#.fieldname @@ -621,7 +606,6 @@ def filter_list(self, input_list, field, check, value, opposite): if str(value).lower() == "null" or str(value).lower() == "none": value = "none" - self.logger.info(f"\nRunning with check \"%s\" on list of length %d\n" % (check, len(input_list))) found_items = [] new_list = [] failed_list = [] @@ -642,10 +626,8 @@ def filter_list(self, input_list, field, check, value, opposite): try: tmp = json.dumps(tmp) except json.decoder.JSONDecodeError as e: - self.logger.info("FAILED DECODING: %s" % e) pass - #self.logger.info("PRE CHECKS FOR TMP: %") # EQUALS JUST FOR STR if check == "equals": @@ -653,15 +635,12 @@ def filter_list(self, input_list, field, check, value, opposite): # value = tmp.lower() if str(tmp).lower() == str(value).lower(): - self.logger.info("APPENDED BECAUSE %s %s %s" % (field, check, value)) new_list.append(item) else: failed_list.append(item) elif check == "equals any of": - self.logger.info("Inside equals any of") checklist = value.split(",") - self.logger.info("Checklist and tmp: %s - %s" % (checklist, tmp)) found = False for subcheck in checklist: subcheck = str(subcheck).strip() @@ -732,7 +711,6 @@ def filter_list(self, input_list, field, check, value, opposite): elif check == "contains any of": value = self.parse_list_internal(value) checklist = value.split(",") - self.logger.info("CHECKLIST: %s. Value: %s" % (checklist, tmp)) found = False for checker in checklist: if str(checker).lower() in str(tmp).lower() or self.check_wildcard(checker, tmp): @@ -745,7 +723,6 @@ def filter_list(self, input_list, field, check, value, opposite): # CONTAINS FIND FOR LIST AND IN FOR STR elif check == "field is unique": - #self.logger.info("FOUND: %s" if tmp.lower() not in found_items: new_list.append(item) found_items.append(tmp.lower()) @@ -761,13 +738,12 @@ def filter_list(self, input_list, field, check, value, opposite): new_list.append(item) list_set = True except AttributeError as e: - self.logger.info("FAILED CHECKING LARGER THAN: %s" % e) pass try: value = len(json.loads(value)) except Exception as e: - self.logger.info(f"[WARNING] Failed to convert destination to list: {e}") + pass try: # Check if it's a list in autocast and if so, check the length @@ -775,7 +751,7 @@ def filter_list(self, input_list, field, check, value, opposite): new_list.append(item) list_set = True except Exception as e: - self.logger.info(f"[WARNING] Failed to check if larger than as list: {e}") + pass if not list_set: failed_list.append(item) @@ -793,13 +769,12 @@ def filter_list(self, input_list, field, check, value, opposite): new_list.append(item) list_set = True except AttributeError as e: - self.logger.info("FAILED CHECKING LARGER THAN: %s" % e) pass try: value = len(json.loads(value)) except Exception as e: - self.logger.info(f"[WARNING] Failed to convert destination to list: {e}") + pass try: # Check if it's a list in autocast and if so, check the length @@ -807,7 +782,7 @@ def filter_list(self, input_list, field, check, value, opposite): new_list.append(item) list_set = True except Exception as e: - self.logger.info(f"[WARNING] Failed to check if larger than as list: {e}") + pass if not list_set: failed_list.append(item) @@ -859,7 +834,6 @@ def filter_list(self, input_list, field, check, value, opposite): failed_list.append(item) except Exception as e: - self.logger.info("[WARNING] FAILED WITH EXCEPTION: %s" % e) failed_list.append(item) # return @@ -954,7 +928,6 @@ def get_file_meta(self, file_id): headers=headers, verify=False, ) - self.logger.info(f"RET: {ret}") return ret.text @@ -963,7 +936,6 @@ def delete_file(self, file_id): headers = { "Authorization": "Bearer %s" % self.authorization, } - self.logger.info("HEADERS: %s" % headers) ret = requests.delete( "%s/api/v1/files/%s?execution_id=%s" @@ -974,8 +946,6 @@ def delete_file(self, file_id): return ret.text def create_file(self, filename, data): - self.logger.info("Inside function") - try: if str(data).startswith("b'") and str(data).endswith("'"): data = data[2:-1] @@ -1013,7 +983,6 @@ def get_file_value(self, filedata): if filedata is None: return "File is empty?" - self.logger.info("INSIDE APP DATA: %s" % filedata) try: return filedata["data"].decode() except: @@ -1064,7 +1033,6 @@ def extract_archive(self, file_id, fileformat="zip", password=None): item = self.get_file(file_id) return_ids = None - self.logger.info("Working with fileformat %s" % fileformat) with tempfile.TemporaryDirectory() as tmpdirname: # Get archive and save phisically @@ -1076,13 +1044,10 @@ def extract_archive(self, file_id, fileformat="zip", password=None): # Zipfile for zipped archive if fileformat.strip().lower() == "zip": try: - self.logger.info("Starting zip extraction") with zipfile.ZipFile(os.path.join(tmpdirname, "archive")) as z_file: if password: - self.logger.info("In zip extraction with password") z_file.setpassword(bytes(password.encode())) - self.logger.info("Past zip extraction") for member in z_file.namelist(): filename = os.path.basename(member) if not filename: @@ -1216,10 +1181,8 @@ def extract_archive(self, file_id, fileformat="zip", password=None): else: return "No such format: %s" % fileformat - self.logger.info("Breaking as this only handles one archive at a time.") if len(to_be_uploaded) > 0: return_ids = self.set_files(to_be_uploaded) - self.logger.info(f"Got return ids from files: {return_ids}") for i in range(len(return_ids)): return_data["archive_id"] = file_id @@ -1239,7 +1202,6 @@ def extract_archive(self, file_id, fileformat="zip", password=None): } ) else: - self.logger.info(f"No file ids to upload.") return_data["success"] = False return_data["files"].append( { @@ -1273,7 +1235,6 @@ def create_archive(self, file_ids, fileformat, name, password=None): "reason": "Make sure to send valid file ids. Example: file_13eea837-c56a-4d52-a067-e673c7186483,file_13eea837-c56a-4d52-a067-e673c7186484", } - self.logger.info("picking {}".format(file_ids)) # GET all items from shuffle items = [self.get_file(file_id) for file_id in file_ids] @@ -1283,14 +1244,12 @@ def create_archive(self, file_ids, fileformat, name, password=None): # Dump files on disk, because libs want path :( with tempfile.TemporaryDirectory() as tmpdir: paths = [] - self.logger.info("Number 1") for item in items: with open(os.path.join(tmpdir, item["filename"]), "wb") as f: f.write(item["data"]) paths.append(os.path.join(tmpdir, item["filename"])) # Create archive temporary - self.logger.info("{} items to inflate".format(len(items))) with tempfile.NamedTemporaryFile() as archive: if fileformat == "zip": @@ -1336,7 +1295,6 @@ def add_list_to_list(self, list_one, list_two): try: list_one = json.loads(list_one) except json.decoder.JSONDecodeError as e: - self.logger.info("Failed to parse list1 as json: %s" % e) if list_one == None: list_one = [] else: @@ -1352,7 +1310,6 @@ def add_list_to_list(self, list_one, list_two): try: list_two = json.loads(list_two) except json.decoder.JSONDecodeError as e: - self.logger.info("Failed to parse list2 as json: %s" % e) if list_one == None: list_one = [] else: @@ -1376,7 +1333,6 @@ def diff_lists(self, list_one, list_two): try: list_one = json.loads(list_one) except json.decoder.JSONDecodeError as e: - self.logger.info("Failed to parse list1 as json: %s" % e) return { "success": False, "reason": "list_one is not a valid list." @@ -1386,7 +1342,6 @@ def diff_lists(self, list_one, list_two): try: list_two = json.loads(list_two) except json.decoder.JSONDecodeError as e: - self.logger.info("Failed to parse list2 as json: %s" % e) return { "success": False, "reason": "list_two is not a valid list." @@ -1433,13 +1388,13 @@ def merge_lists(self, list_one, list_two, set_field="", sort_key_list_one="", so try: list_one = json.loads(list_one) except json.decoder.JSONDecodeError as e: - self.logger.info("Failed to parse list1 as json: %s" % e) + pass if isinstance(list_two, str): try: list_two = json.loads(list_two) except json.decoder.JSONDecodeError as e: - self.logger.info("Failed to parse list2 as json: %s" % e) + pass if not isinstance(list_one, list) or not isinstance(list_two, list): if isinstance(list_one, dict) and isinstance(list_two, dict): @@ -1454,19 +1409,15 @@ def merge_lists(self, list_one, list_two, set_field="", sort_key_list_one="", so return {"success": False, "message": "Lists length must be the same. %d vs %d" % (len(list_one), len(list_two))} if len(sort_key_list_one) > 0: - self.logger.info("Sort 1 %s by key: %s" % (list_one, sort_key_list_one)) try: list_one = sorted(list_one, key=lambda k: k.get(sort_key_list_one), reverse=True) except: - self.logger.info("Failed to sort list one") pass if len(sort_key_list_two) > 0: - #self.logger.info("Sort 2 %s by key: %s" % (list_two, sort_key_list_two)) try: list_two = sorted(list_two, key=lambda k: k.get(sort_key_list_two), reverse=True) except: - self.logger.info("Failed to sort list one") pass # Loops for each item in sub array and merges items together @@ -1474,16 +1425,13 @@ def merge_lists(self, list_one, list_two, set_field="", sort_key_list_one="", so base_key = "shuffle_auto_merge" try: for i in range(len(list_one)): - #self.logger.info(list_two[i]) if isinstance(list_two[i], dict): for key, value in list_two[i].items(): list_one[i][key] = value elif isinstance(list_two[i], str) and list_two[i] == "": continue elif isinstance(list_two[i], str) or isinstance(list_two[i], int) or isinstance(list_two[i], bool): - self.logger.info("IN SETTER FOR %s" % list_two[i]) if len(set_field) == 0: - self.logger.info("Define a JSON key to set for List two (Set Field)") list_one[i][base_key] = list_two[i] else: set_field = set_field.replace(" ", "_", -1) @@ -1561,13 +1509,6 @@ def xml_json_convertor(self, convertto, data): } def date_to_epoch(self, input_data, date_field, date_format): - - self.logger.info( - "Executing with {} on {} with format {}".format( - input_data, date_field, date_format - ) - ) - if isinstance(input_data, str): result = json.loads(input_data) else: @@ -1683,7 +1624,6 @@ def compare_relative_date( def run_math_operation(self, operation): - self.logger.info("Operation: %s" % operation) result = eval(operation) return result @@ -1694,8 +1634,6 @@ def escape_html(self, input_data): else: mapping = input_data - self.logger.info(f"Got mapping {json.dumps(mapping, indent=2)}") - result = markupsafe.escape(mapping) return mapping @@ -1715,7 +1653,7 @@ def check_cache_contains(self, key, value, append): try: value = json.dumps(value) except Exception as e: - self.logger.info(f"[WARNING] Error in JSON dumping (cache contains): {e}") + pass if not isinstance(value, str): value = str(value) @@ -1856,8 +1794,6 @@ def check_cache_contains(self, key, value, append): "search": value, "key": key } - - self.logger.info("Handle all values!") #return allvalues @@ -1891,6 +1827,7 @@ def change_cache_subkey(self, key, subkey, value, overwrite): value = json.dumps(value) except Exception as e: self.logger.info(f"[WARNING] Error in JSON dumping (set cache): {e}") + elif not isinstance(value, str): value = str(value) @@ -1940,9 +1877,7 @@ def get_cache_value(self, key): value = requests.post(url, json=data, verify=False) try: allvalues = value.json() - #self.logger.info("VAL1: ", allvalues) allvalues["key"] = key - #self.logger.info("VAL2: ", allvalues) if allvalues["success"] == True and len(allvalues["value"]) > 0: allvalues["found"] = True @@ -1955,7 +1890,6 @@ def get_cache_value(self, key): allvalues["value"] = parsedvalue except: - self.logger.info("Parsing of value as JSON failed") pass return json.dumps(allvalues) @@ -2026,7 +1960,6 @@ def convert_json_to_tags(self, json_object, split_value=", ", include_key=True, parsedstring = [] try: for key, value in json_object.items(): - self.logger.info("KV: %s:%s" % (key, value)) if isinstance(value, str) or isinstance(value, int) or isinstance(value, bool): if include_key == True: parsedstring.append("%s:%s" % (key, value)) @@ -2047,15 +1980,11 @@ def convert_json_to_tags(self, json_object, split_value=", ", include_key=True, return fullstring def cidr_ip_match(self, ip, networks): - self.logger.info("Executing with\nIP: {},\nNetworks: {}".format(ip, networks)) if isinstance(networks, str): try: networks = json.loads(networks) except json.decoder.JSONDecodeError as e: - self.logger.info("Failed to parse networks list as json: {}. Type: {}".format( - e, type(networks) - )) return { "success": False, "reason": "Networks is not a valid list: {}".format(networks), @@ -2079,7 +2008,7 @@ def cidr_ip_match(self, ip, networks): def get_timestamp(self, time_format): timestamp = int(time.time()) if time_format == "unix" or time_format == "epoch": - self.logger.info("Running default timestamp %s" % timestamp) + pass return timestamp @@ -2090,12 +2019,12 @@ def get_hash_sum(self, value): try: md5_value = hashlib.md5(str(value).encode('utf-8')).hexdigest() except Exception as e: - self.logger.info(f"Error in md5sum: {e}") + pass try: sha256_value = hashlib.sha256(str(value).encode('utf-8')).hexdigest() except Exception as e: - self.logger.info(f"Error in sha256: {e}") + pass parsedvalue = { "success": True, @@ -2465,7 +2394,7 @@ def parse_ioc(self, input_string, input_type="all"): try: item["is_private_ip"] = ipaddress.ip_address(item["data"]).is_private except: - self.logger.info("Error parsing %s" % item["data"]) + pass try: newarray = json.dumps(newarray) From 1a10115df2bccbcef91af4bb1eee2e5fb3696744 Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 29 Feb 2024 13:56:36 +0100 Subject: [PATCH 098/259] Added local cache for check cache contains in case we are checking with a list. Reduces api requests drastically, but MAY cause minor inconsistencies --- shuffle-tools/1.2.0/src/app.py | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 740edbd5..12464bee 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -1649,6 +1649,20 @@ def check_cache_contains(self, key, value, append): "key": key, } + allvalues = {} + try: + for item in self.local_storage: + if item["execution_id"] == self.current_execution_id and item["key"] == key: + # Max keeping the local cache properly for 5 seconds due to workflow continuations + elapsed_time = time.time() - item["time_set"] + if elapsed_time > 5: + break + + allvalues = item["data"] + + except Exception as e: + print("[ERROR] Failed cache contains for current execution id local storage: %s" % e) + if isinstance(value, dict) or isinstance(value, list): try: value = json.dumps(value) @@ -1665,9 +1679,13 @@ def check_cache_contains(self, key, value, append): else: append = False - get_response = requests.post(url, json=data, verify=False) + if "success" not in allvalues: + get_response = requests.post(url, json=data, verify=False) + try: - allvalues = get_response.json() + if "success" not in allvalues: + allvalues = get_response.json() + try: if allvalues["value"] == None or allvalues["value"] == "null": allvalues["value"] = "[]" @@ -1686,6 +1704,7 @@ def check_cache_contains(self, key, value, append): #allvalues["key"] = key #return allvalues + return { "success": True, "found": False, @@ -1727,6 +1746,14 @@ def check_cache_contains(self, key, value, append): #return "%s %s" % (item, value) if item == value: if not append: + try: + newdata = json.loads(json.dumps(data)) + newdata["time_set"] = time.time() + newdata["data"] = allvalues + self.local_storage.append(newdata) + except Exception as e: + print("[ERROR] Failed in local storage append: %s" % e) + return { "success": True, "found": True, @@ -1798,6 +1825,7 @@ def check_cache_contains(self, key, value, append): #return allvalues except Exception as e: + print("[ERROR] Failed check cache contains: %s" % e) return { "success": False, "key": key, From 881a6054815b3b20187daa3ca8d58950721c96da Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 11 Mar 2024 21:06:05 +0100 Subject: [PATCH 099/259] Added a branch result auto merger to make adding more branches to something dynamic --- shuffle-tools/1.2.0/api.yaml | 12 ++++++ shuffle-tools/1.2.0/src/app.py | 67 ++++++++++++++++++++++++++++++++-- 2 files changed, 76 insertions(+), 3 deletions(-) diff --git a/shuffle-tools/1.2.0/api.yaml b/shuffle-tools/1.2.0/api.yaml index b67bc534..b792959a 100644 --- a/shuffle-tools/1.2.0/api.yaml +++ b/shuffle-tools/1.2.0/api.yaml @@ -1094,6 +1094,18 @@ actions: - false schema: type: string + - name: merge_incoming_branches + description: 'Merges the data of incoming branches. Uses the input type to determine how to merge the data, and removes duplicates' + parameters: + - name: input_type + description: What type to use + required: false + multiline: false + example: 'list' + options: + - list + schema: + type: string - name: run_ssh_command description: 'Run a command on remote machine with SSH' parameters: diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 12464bee..bc4e10a5 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -1845,7 +1845,6 @@ def check_cache_contains(self, key, value, append): ## subkey = "hi", value = "test3", overwrite=False ## {"subkey": "hi", "value": ["test2", "test3"]} - #def set_cache_value(self, key, value): def change_cache_subkey(self, key, subkey, value, overwrite): org_id = self.full_execution["workflow"]["execution_org"]["id"] url = "%s/api/v1/orgs/%s/set_cache" % (self.url, org_id) @@ -1925,7 +1924,6 @@ def get_cache_value(self, key): self.logger.info("Value couldn't be parsed, or json dump of value failed") return value.text - # FIXME: Add option for org only & sensitive data (not to be listed) def set_cache_value(self, key, value): org_id = self.full_execution["workflow"]["execution_org"]["id"] url = "%s/api/v1/orgs/%s/set_cache" % (self.url, org_id) @@ -2020,7 +2018,7 @@ def cidr_ip_match(self, ip, networks): try: ip_networks = list(map(ipaddress.ip_network, networks)) - ip_address = ipaddress.ip_address(ip) + ip_address = ipaddress.ip_address(ip, False) except ValueError as e: return "IP or some networks are not in valid format.\nError: {}".format(e) @@ -2577,6 +2575,69 @@ def parse_ioc_new(self, input_string, input_type="all"): return newarray + def merge_incoming_branches(self, input_type="list"): + wf = self.full_execution["workflow"] + if "branches" not in wf or not wf["branches"]: + return { + "success": False, + "reason": "No branches found" + } + + if "results" not in self.full_execution or not self.full_execution["results"]: + return { + "success": False, + "reason": "No results for previous actions not found" + } + + if not input_type: + input_type = "list" + + branches = wf["branches"] + cur_action = self.action + #print("Found %d branches" % len(branches)) + + results = [] + for branch in branches: + if branch["destination_id"] != cur_action["id"]: + continue + + # Find result for the source + source_id = branch["source_id"] + + for res in self.full_execution["results"]: + if res["action"]["id"] != source_id: + continue + + try: + parsed = json.loads(res["result"]) + results.append(parsed) + except Exception as e: + results.append(res["result"]) + + break + + if input_type == "list": + newlist = [] + for item in results: + if not isinstance(item, list): + continue + + for subitem in item: + if subitem in newlist: + continue + + newlist.append(subitem) + #newlist.append(item) + + results = newlist + else: + return { + "success": False, + "reason": "No results from source branches with type %s" % input_type + } + + return results + def list_cidr_ips(self, cidr): defaultreturn = { "success": False, From c3b3585f2b354fb87f3950b7e5771e7fb0e39d0c Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 25 Mar 2024 12:56:37 +0100 Subject: [PATCH 100/259] Pushing email stuff --- email/1.2.0/requirements.txt | 2 +- email/1.2.0/src/app.py | 3 +-- email/1.3.0/api.yaml | 19 +++++++++++++++++++ email/1.3.0/src/app.py | 19 ++++++++++++++++++- shuffle-tools/1.2.0/src/app.py | 12 +++++++++++- 5 files changed, 50 insertions(+), 5 deletions(-) diff --git a/email/1.2.0/requirements.txt b/email/1.2.0/requirements.txt index 926027e8..7f8dacf7 100644 --- a/email/1.2.0/requirements.txt +++ b/email/1.2.0/requirements.txt @@ -1,6 +1,6 @@ requests==2.25.1 glom==20.11.0 -eml-parser==1.17.0 +eml-parser==1.17.5 msg-parser==1.2.0 mail-parser==3.15.0 extract-msg==0.30.9 diff --git a/email/1.2.0/src/app.py b/email/1.2.0/src/app.py index 4a27c9b2..23993bf8 100644 --- a/email/1.2.0/src/app.py +++ b/email/1.2.0/src/app.py @@ -391,10 +391,9 @@ def parse_email_file(self, file_id, file_extension): "reason": "Couldn't get file with ID %s" % file_id } - print("File: %s" % file_path) if file_extension.lower() == 'eml': print('working with .eml file') - ep = eml_parser.EmlParser(include_attachment_data=True, include_raw_body=True, parse_attachment=True) + ep = eml_parser.EmlParser(include_attachment_data=True, include_raw_body=True, parse_attachments=True) try: parsed_eml = ep.decode_email_bytes(file_path['data']) if str(parsed_eml["header"]["date"]) == "1970-01-01 00:00:00+00:00": diff --git a/email/1.3.0/api.yaml b/email/1.3.0/api.yaml index 40998adf..017222d7 100644 --- a/email/1.3.0/api.yaml +++ b/email/1.3.0/api.yaml @@ -233,6 +233,25 @@ actions: required: false schema: type: bool + - name: parse_eml + description: Takes an eml string and parses it to JSON + parameters: + - name: filedata + description: The EML string data + required: true + multiline: true + example: 'EML string data' + schema: + type: string + - name: extract_attachments + description: Whether to extract the attachments straight into files + required: true + options: + - true + - false + example: 'true' + schema: + type: string - name: parse_email_file description: Takes a file from shuffle and analyzes it if it's a valid .eml or .msg parameters: diff --git a/email/1.3.0/src/app.py b/email/1.3.0/src/app.py index 62b3a26d..f80a17c0 100644 --- a/email/1.3.0/src/app.py +++ b/email/1.3.0/src/app.py @@ -384,8 +384,25 @@ def merge(d1, d2): "messages": json.dumps(emails, default=default), } + def parse_eml(self, filedata, extract_attachments=False): + parsedfile = { + "success": True, + "filename": "email.eml", + "data": filedata, + } + + return self.parse_email_file(parsedfile, extract_attachments) + def parse_email_file(self, file_id, extract_attachments=False): - file_path = self.get_file(file_id) + file_path = { + "success": False, + } + + if isinstance(file_id, dict) and "data" in file_id: + file_path = file_id + else: + file_path = self.get_file(file_id) + if file_path["success"] == False: return { "success": False, diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index bc4e10a5..c68c006d 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -981,7 +981,16 @@ def list_file_category_ids(self, file_category): def get_file_value(self, filedata): filedata = self.get_file(filedata) if filedata is None: - return "File is empty?" + return { + "success": False, + "reason": "File not found", + } + + if "data" not in filedata: + return { + "success": False, + "reason": "File content not found. File might be empty or not exist", + } try: return filedata["data"].decode() @@ -998,6 +1007,7 @@ def get_file_value(self, filedata): return { "success": False, "reason": "Got the file, but the encoding can't be printed", + "size": len(filedata["data"]), } def download_remote_file(self, url, custom_filename=""): From db82f75bca0a3e944d983e458a05e642410d40dc Mon Sep 17 00:00:00 2001 From: ausef <62292266+ausef@users.noreply.github.com> Date: Thu, 11 Apr 2024 10:29:19 +0200 Subject: [PATCH 101/259] Update requirements.txt --- velociraptor/1.0.0/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/velociraptor/1.0.0/requirements.txt b/velociraptor/1.0.0/requirements.txt index ad6e959c..7698bd2c 100644 --- a/velociraptor/1.0.0/requirements.txt +++ b/velociraptor/1.0.0/requirements.txt @@ -1 +1 @@ -pyvelociraptor==0.1.6 +pyvelociraptor==0.1.8 From d2d16c84e8f9f773f31d2bb5828f0b19d2ee955e Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 12 Apr 2024 17:45:24 +0200 Subject: [PATCH 102/259] Fixed email, ai & tools parsing app bugs --- email/1.3.0/src/app.py | 13 +++++ shuffle-ai/1.0.0/api.yaml | 30 +++++++++++ shuffle-ai/1.0.0/requirements.txt | 1 + shuffle-ai/1.0.0/src/app.py | 85 ++++++++++++++++++++++++++++--- shuffle-tools/1.2.0/src/app.py | 2 + 5 files changed, 124 insertions(+), 7 deletions(-) diff --git a/email/1.3.0/src/app.py b/email/1.3.0/src/app.py index f80a17c0..62ecfef2 100644 --- a/email/1.3.0/src/app.py +++ b/email/1.3.0/src/app.py @@ -409,6 +409,19 @@ def parse_email_file(self, file_id, extract_attachments=False): "reason": "Couldn't get file with ID %s" % file_id } + #print("PRE: ", file_path) + + # Check if data is in base64 and decode it + # If it ends with = then it may be bas64 + + if str(file_path["data"]).endswith("="): + try: + file_path["data"] = base64.b64decode(file_path["data"]) + except Exception as e: + print(f"Failed to decode base64: {e}") + + #print("POST: ", file_path) + #print("File: %s" % file_path) print('working with .eml file? %s' % file_path["filename"]) diff --git a/shuffle-ai/1.0.0/api.yaml b/shuffle-ai/1.0.0/api.yaml index ae0ae960..a247fad5 100644 --- a/shuffle-ai/1.0.0/api.yaml +++ b/shuffle-ai/1.0.0/api.yaml @@ -98,6 +98,36 @@ actions: returns: schema: type: string + - name: run_schemaless + description: Runs an automatically translated action + parameters: + - name: category + description: The category the action is in + required: true + multiline: false + schema: + type: string + - name: action + description: The action label to run + required: true + multiline: false + schema: + type: string + - name: app_name + description: The app to run the action in + required: false + multiline: false + schema: + type: string + - name: fields + description: The additional fields to add + required: false + multiline: false + schema: + type: string + returns: + schema: + type: string - name: transcribe_audio description: Returns text from audio parameters: diff --git a/shuffle-ai/1.0.0/requirements.txt b/shuffle-ai/1.0.0/requirements.txt index b1fb92b5..a783f817 100644 --- a/shuffle-ai/1.0.0/requirements.txt +++ b/shuffle-ai/1.0.0/requirements.txt @@ -1,3 +1,4 @@ pytesseract pdf2image pypdf2 +requests diff --git a/shuffle-ai/1.0.0/src/app.py b/shuffle-ai/1.0.0/src/app.py index 4a76c673..42e6e09c 100644 --- a/shuffle-ai/1.0.0/src/app.py +++ b/shuffle-ai/1.0.0/src/app.py @@ -1,8 +1,9 @@ -import pytesseract -from pdf2image import convert_from_path -import PyPDF2 import json +import PyPDF2 import tempfile +import requests +import pytesseract +from pdf2image import convert_from_path from walkoff_app_sdk.app_base import AppBase @@ -59,10 +60,6 @@ def generate_report(self, apikey, input_data, report_title, report_name="generat report_name = report_name + ".html" report_name = report_name.replace(" ", "_", -1) - - if not formatting: - formatting = "auto" - output_formatting= "Format the following text into an HTML report with relevant graphs and tables. Title of the report should be {report_title}." ret = requests.post( "https://shuffler.io/api/v1/conversation", @@ -217,5 +214,79 @@ def gpt(self, input_text): "reason": "Not implemented yet" } + def run_schemaless(self, category, action, app_name="", fields=""): + """ + action := shuffle.CategoryAction{ + Label: step.Name, + Category: step.Category, + AppName: step.AppName, + Fields: step.Fields, + + Environment: step.Environment, + + SkipWorkflow: true, + } + """ + + data = { + "label": action, + "category": category, + + "app_name": "", + "fields": [], + + "skip_workflow": True, + } + + if app_name: + data["app_name"] = app_name + + if fields: + if isinstance(fields, list): + data["fields"] = fields + + elif isinstance(fields, dict): + for key, value in fields.items(): + data["fields"].append({ + "key": key, + "value": str(value), + }) + + else: + try: + loadedfields = json.loads(fields) + for key, value in loadedfields.items(): + data["fields"].append({ + "key": key, + "value": value, + }) + + except Exception as e: + print("[ERROR] Failed to load fields as JSON: %s" % e) + return json.dumps({ + "success": False, + "reason": "Ensure Fields are valid JSON", + "type": type(fields), + "details": "%s" % e, + }) + + + baseurl = "%s/api/v1/apps/categories/run" % self.base_url + baseurl += "?execution_id=%s&authorization=%s" % (self.current_execution_id, self.authorization) + + print("[DEBUG] Running schemaless action with URL '%s', category %s and action label %s" % (baseurl, category, action)) + + headers = {} + request = requests.post( + baseurl, + json=data, + headers=headers, + ) + + try: + return request.json() + except: + return request.text + if __name__ == "__main__": Tools.run() diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index c68c006d..45160b86 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -2394,6 +2394,8 @@ def parse_ioc(self, input_string, input_type="all"): input_type = input_type.split(",") for item in input_type: item = item.strip() + if not item.endswith("s"): + item = "%ss" % item ioc_types = input_type From 655302ff7d80f341892fb0ccbdb5aeae9a95186f Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 17 Apr 2024 17:51:33 +0200 Subject: [PATCH 103/259] Fixed timeout bug in PATCH HTTP action --- http/1.4.0/src/app.py | 5 +++++ shuffle-tools/1.2.0/api.yaml | 2 ++ 2 files changed, 7 insertions(+) diff --git a/http/1.4.0/src/app.py b/http/1.4.0/src/app.py index ff2ec91b..865d223e 100755 --- a/http/1.4.0/src/app.py +++ b/http/1.4.0/src/app.py @@ -304,6 +304,11 @@ def PATCH(self, url, headers="", body="", username="", password="", verify=True, else: auth = requests.auth.HTTPBasicAuth(username, password) + if not timeout: + timeout = 5 + if timeout: + timeout = int(timeout) + if to_file == "true": to_file = True else: diff --git a/shuffle-tools/1.2.0/api.yaml b/shuffle-tools/1.2.0/api.yaml index b792959a..a31a363d 100644 --- a/shuffle-tools/1.2.0/api.yaml +++ b/shuffle-tools/1.2.0/api.yaml @@ -282,6 +282,8 @@ actions: required: false multiline: false example: "domains,urls,email_addresses,ipv4s,ipv4_cidrs,ipv6s,md5s,sha256s,sha1s,cves" + value: "domains,urls,ipv4s,md5s,sha1s" + multiselect: true schema: type: string returns: From d46b22098be0b487cc1a136a9f65beae54b4ccbf Mon Sep 17 00:00:00 2001 From: dhaval055 Date: Fri, 19 Apr 2024 10:23:20 +0000 Subject: [PATCH 104/259] Updated send_email_smtp action to support cc --- email/1.3.0/api.yaml | 7 +++++++ email/1.3.0/src/app.py | 6 +++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/email/1.3.0/api.yaml b/email/1.3.0/api.yaml index 017222d7..3a822c6a 100644 --- a/email/1.3.0/api.yaml +++ b/email/1.3.0/api.yaml @@ -83,6 +83,13 @@ actions: required: true schema: type: string + - name: cc_emails + description: cc_emails + multiline: false + example: "test@gmail.com" + required: false + schema: + type: string - name: subject description: The subject of the email multiline: false diff --git a/email/1.3.0/src/app.py b/email/1.3.0/src/app.py index 62ecfef2..4d92dbee 100644 --- a/email/1.3.0/src/app.py +++ b/email/1.3.0/src/app.py @@ -75,7 +75,7 @@ def send_email_shuffle(self, apikey, recipients, subject, body): return requests.post(url, headers=headers, json=data).text def send_email_smtp( - self, smtp_host, recipient, subject, body, smtp_port, attachments="", username="", password="", ssl_verify="True", body_type="html" + self, smtp_host, recipient, cc_emails ,subject, body, smtp_port, attachments="", username="", password="", ssl_verify="True", body_type="html", ): if type(smtp_port) == str: try: @@ -112,6 +112,10 @@ def send_email_smtp( msg["From"] = username msg["To"] = recipient msg["Subject"] = subject + + if cc_emails: + msg["cc"] = cc_emails + msg.attach(MIMEText(body, body_type)) # Read the attachments From b03e318514c008164ff8f66e3f1005f0551c5627 Mon Sep 17 00:00:00 2001 From: dhaval055 Date: Fri, 19 Apr 2024 10:57:38 +0000 Subject: [PATCH 105/259] fixed success message --- email/1.3.0/api.yaml | 2 +- email/1.3.0/src/app.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/email/1.3.0/api.yaml b/email/1.3.0/api.yaml index 3a822c6a..12679e9d 100644 --- a/email/1.3.0/api.yaml +++ b/email/1.3.0/api.yaml @@ -86,7 +86,7 @@ actions: - name: cc_emails description: cc_emails multiline: false - example: "test@gmail.com" + example: "frikky@shuffler.io,frikky@shuffler.io" required: false schema: type: string diff --git a/email/1.3.0/src/app.py b/email/1.3.0/src/app.py index 4d92dbee..0da967ce 100644 --- a/email/1.3.0/src/app.py +++ b/email/1.3.0/src/app.py @@ -114,7 +114,7 @@ def send_email_smtp( msg["Subject"] = subject if cc_emails: - msg["cc"] = cc_emails + msg["Cc"] = cc_emails msg.attach(MIMEText(body, body_type)) @@ -165,7 +165,7 @@ def send_email_smtp( self.logger.info("Successfully sent email with subject %s to %s" % (subject, recipient)) return { "success": True, - "reason": "Email sent to %s!" % recipient, + "reason": "Email sent to %s, %s!" %(recipient,cc_emails) if cc_emails else "Email sent to %s!" % recipient, "attachments": attachment_count } From 9a75d9536f790ec17d09a6e4d857015ca8c387a9 Mon Sep 17 00:00:00 2001 From: dhaval055 Date: Mon, 22 Apr 2024 09:32:42 +0000 Subject: [PATCH 106/259] made cc_emails field optional in Python --- email/1.3.0/src/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/email/1.3.0/src/app.py b/email/1.3.0/src/app.py index 0da967ce..0b7feb45 100644 --- a/email/1.3.0/src/app.py +++ b/email/1.3.0/src/app.py @@ -75,7 +75,7 @@ def send_email_shuffle(self, apikey, recipients, subject, body): return requests.post(url, headers=headers, json=data).text def send_email_smtp( - self, smtp_host, recipient, cc_emails ,subject, body, smtp_port, attachments="", username="", password="", ssl_verify="True", body_type="html", + self, smtp_host, recipient, subject, body, smtp_port, attachments="", username="", password="", ssl_verify="True", body_type="html", cc_emails="" ): if type(smtp_port) == str: try: @@ -113,7 +113,7 @@ def send_email_smtp( msg["To"] = recipient msg["Subject"] = subject - if cc_emails: + if cc_emails != None and len(cc_emails) > 0: msg["Cc"] = cc_emails msg.attach(MIMEText(body, body_type)) From a4c794b4d1bf301fb3b842fbaec984d184e7a4a0 Mon Sep 17 00:00:00 2001 From: BenM Date: Wed, 24 Apr 2024 13:28:18 +0100 Subject: [PATCH 107/259] Add new functions to microsoft-identity-and-access --- microsoft-identity-and-access/1.0.0/api.yaml | 106 +++++++++++++ .../1.0.0/src/app.py | 144 ++++++++++++++++++ 2 files changed, 250 insertions(+) diff --git a/microsoft-identity-and-access/1.0.0/api.yaml b/microsoft-identity-and-access/1.0.0/api.yaml index 9a3e015d..b891edc1 100644 --- a/microsoft-identity-and-access/1.0.0/api.yaml +++ b/microsoft-identity-and-access/1.0.0/api.yaml @@ -326,5 +326,111 @@ actions: required: true schema: type: string + - name: disable_user_account + description: Disable user account + parameters: + - name: user_email_or_id + description: User Email or Object ID + multiline: false + example: "Test.User@example.com" + required: true + schema: + type: string + - name: update_user_job_title + description: Updates user Job Title field + parameters: + - name: user_email_or_id + description: User Email or Object ID + multiline: false + example: "Test.user@example.com" + required: true + schema: + type: string + - name: user_job_title + description: Job Title to update for user + multiline: false + example: "DevOps Engineer" + required: true + schema: + type: string + - name: update_user_department + description: Updates user Department field + parameters: + - name: user_email_or_id + description: User Email or Object ID + multiline: false + example: "Test.user@example.com" + required: true + schema: + type: string + - name: user_department + description: Department to update for user + multiline: false + example: "Finance Department" + required: true + schema: + type: string + - name: update_user_employee_type + description: Updates user Employee Type field + parameters: + - name: user_email_or_id + description: User Email or Object ID + multiline: false + example: "Test.user@example.com" + required: true + schema: + type: string + - name: user_employee_type + description: Employee Type to update for user + multiline: false + example: "Contractor" + required: true + schema: + type: string + - name: update_user_leave_date + description: Updates user Leave Date field + parameters: + - name: user_email_or_id + description: User Email or Object ID + multiline: false + example: "Test.user@example.com" + required: true + schema: + type: string + - name: user_leave_date + description: User Leave Date + multiline: false + example: "2022-09-30T23:59:59Z" + required: true + schema: + type: string + - name: get_user_direct_groups + description: Retrieves Static Groups User is Member Of + parameters: + - name: user_email_or_id + description: User Email or Object ID + multiline: false + example: "Test.user@example.com" + required: true + schema: + type: string + - name: remove_user_from_group + description: Removes User from Specified Group + parameters: + - name: user_id + description: Object ID of User + multiline: false + example: eb6fa72b-f4f0-4ce0-94d2-dd16b4a22686 + required: true + schema: + type: string + - name: group_id + description: Object ID of Group + multiline: false + example: 2a712b67-91af-429f-9603-a5bfhgu7b151 + required: true + schema: + type: string + large_image: data:image/png;base64,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 diff --git a/microsoft-identity-and-access/1.0.0/src/app.py b/microsoft-identity-and-access/1.0.0/src/app.py index 07a791ad..19f7729f 100644 --- a/microsoft-identity-and-access/1.0.0/src/app.py +++ b/microsoft-identity-and-access/1.0.0/src/app.py @@ -498,5 +498,149 @@ def reset_user_password(self, tenant_id, client_id, client_secret, user_email_or return {"success": False, "reason": "Bad status code %d - expecting 200." % ret.status_code, "error_response":ret.text} + def disable_user_account(self, tenant_id, client_id, client_secret, user_email_or_id): + graph_url = "https://graph.microsoft.com" + session = self.authenticate(tenant_id, client_id, client_secret, graph_url) + + graph_url = f"https://graph.microsoft.com/beta/users/{user_email_or_id}" + + headers = { + "Content-type": "application/json" + } + request_body = { + "accountEnabled": "False" + } + + ret = session.patch(graph_url, json=request_body,headers=headers) + print(ret.status_code) + print(ret.text) + if ret.status_code < 300: + data = ret.json() + return data + + return {"success": False, "reason": "Bad status code %d - expecting 200." % ret.status_code, "error_response":ret.text} + + def update_user_job_title(self, tenant_id, client_id, client_secret, user_email_or_id, user_job_title): + graph_url = "https://graph.microsoft.com" + session = self.authenticate(tenant_id, client_id, client_secret, graph_url) + + graph_url = f"https://graph.microsoft.com/beta/users/{user_email_or_id}" + + headers = { + "Content-type": "application/json" + } + request_body = { + "jobTitle": user_job_title + } + + ret = session.patch(graph_url, json=request_body,headers=headers) + print(ret.status_code) + print(ret.text) + if ret.status_code < 300: + data = ret.json() + return data + + return {"success": False, "reason": "Bad status code %d - expecting 200." % ret.status_code, "error_response":ret.text} + + def update_user_department(self, tenant_id, client_id, client_secret, user_email_or_id, user_department): + graph_url = "https://graph.microsoft.com" + session = self.authenticate(tenant_id, client_id, client_secret, graph_url) + + graph_url = f"https://graph.microsoft.com/beta/users/{user_email_or_id}" + + headers = { + "Content-type": "application/json" + } + request_body = { + "department": user_department + } + + ret = session.patch(graph_url, json=request_body,headers=headers) + print(ret.status_code) + print(ret.text) + if ret.status_code < 300: + data = ret.json() + return data + + return {"success": False, "reason": "Bad status code %d - expecting 200." % ret.status_code, "error_response":ret.text} + + def update_user_employee_type(self, tenant_id, client_id, client_secret, user_email_or_id, user_employee_type): + graph_url = "https://graph.microsoft.com" + session = self.authenticate(tenant_id, client_id, client_secret, graph_url) + + graph_url = f"https://graph.microsoft.com/beta/users/{user_email_or_id}" + + headers = { + "Content-type": "application/json" + } + request_body = { + "employeeType": user_employee_type + } + + ret = session.patch(graph_url, json=request_body,headers=headers) + print(ret.status_code) + print(ret.text) + if ret.status_code < 300: + data = ret.json() + return data + + return {"success": False, "reason": "Bad status code %d - expecting 200." % ret.status_code, "error_response":ret.text} + + def update_user_leave_date(self, tenant_id, client_id, client_secret, user_email_or_id, user_leave_date): + graph_url = "https://graph.microsoft.com" + session = self.authenticate(tenant_id, client_id, client_secret, graph_url) + + graph_url = f"https://graph.microsoft.com/beta/users/{user_email_or_id}" + + headers = { + "Content-type": "application/json" + } + request_body = { + "employeeLeaveDateTime": user_leave_date + } + + ret = session.patch(graph_url, json=request_body,headers=headers) + print(ret.status_code) + print(ret.text) + if ret.status_code < 300: + data = ret.json() + return data + + return {"success": False, "reason": "Bad status code %d - expecting 200." % ret.status_code, "error_response":ret.text} + + def get_user_direct_groups(self, tenant_id, client_id, client_secret, user_email_or_id): + graph_url = "https://graph.microsoft.com" + session = self.authenticate(tenant_id, client_id, client_secret, graph_url) + + graph_url = f"https://graph.microsoft.com/beta/users/{user_email_or_id}/memberOf?$filter=NOT(groupTypes/any(c:c eq 'DynamicMembership'))&$count=true" + + headers = { + "ConsistencyType": "eventual" + } + + ret = session.get(graph_url,headers=headers) + print(ret.status_code) + print(ret.text) + if ret.status_code < 300: + data = ret.json() + return data + + return {"success": False, "reason": "Bad status code %d - expecting 200." % ret.status_code, "error_response":ret.text} + + def remove_user_from_group(self, tenant_id, client_id, client_secret, user_id, group_id): + graph_url = "https://graph.microsoft.com" + session = self.authenticate(tenant_id, client_id, client_secret, graph_url) + + graph_url = f"https://graph.microsoft.com/beta/groups/{group_id}/members/{user_id}/$ref" + + ret = session.delete(graph_url) + print(ret.status_code) + print(ret.text) + if ret.status_code < 300: + data = ret.json() + return data + + return {"success": False, "reason": "Bad status code %d - expecting 200." % ret.status_code, "error_response":ret.text} + if __name__ == "__main__": MsIdentityAccess.run() From f7158f055a548a5c3bf5b33c5e51834ab3287399 Mon Sep 17 00:00:00 2001 From: dhaval055 Date: Mon, 29 Apr 2024 06:51:09 +0000 Subject: [PATCH 108/259] added gws app --- gws/1.0.0/Dockerfile | 26 ++++++++ gws/1.0.0/api.yaml | 106 ++++++++++++++++++++++++++++++ gws/1.0.0/requirements.txt | 6 ++ gws/1.0.0/src/app.py | 130 +++++++++++++++++++++++++++++++++++++ 4 files changed, 268 insertions(+) create mode 100644 gws/1.0.0/Dockerfile create mode 100644 gws/1.0.0/api.yaml create mode 100644 gws/1.0.0/requirements.txt create mode 100644 gws/1.0.0/src/app.py diff --git a/gws/1.0.0/Dockerfile b/gws/1.0.0/Dockerfile new file mode 100644 index 00000000..364e1531 --- /dev/null +++ b/gws/1.0.0/Dockerfile @@ -0,0 +1,26 @@ +# Base our app image off of the WALKOFF App SDK image +FROM frikky/shuffle:app_sdk as base + +# We're going to stage away all of the bloat from the build tools so lets create a builder stage +FROM base as builder + +# Install all alpine build tools needed for our pip installs +RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev + +# Install all of our pip packages in a single directory that we can copy to our base image later +RUN mkdir /install +WORKDIR /install +COPY requirements.txt /requirements.txt +RUN pip install --prefix="/install" -r /requirements.txt + +# Switch back to our base image and copy in all of our built packages and source code +FROM base +COPY --from=builder /install /usr/local +COPY src /app + +# Install any binary dependencies needed in our final image +# RUN apk --no-cache add --update my_binary_dependency + +# Finally, lets run our app! +WORKDIR /app +CMD python app.py --log-level DEBUG diff --git a/gws/1.0.0/api.yaml b/gws/1.0.0/api.yaml new file mode 100644 index 00000000..a879728b --- /dev/null +++ b/gws/1.0.0/api.yaml @@ -0,0 +1,106 @@ +app_version: 1.0.0 +name: Google Workspace +description: Manage Google Workspace with Shuffle +contact_info: + name: "dhaval055" + url: https://github.com/dhaval055 +tags: + - Assets +categories: + - Assets +authentication: + required: true + parameters: + - name: service_account_file_id + description: Upload a service account file to Shuffle and use the file ID here. + example: "file_id" + required: true + schema: + type: string + - name: subject + description: User email associated with service account. + example: "admin@org.com" + required: true + schema: + type: string +actions: + - name: reset_user_password + description: Change GWS user password. + parameters: + - name: user_email + description: User email you want to reset password of. + required: true + multiline: false + example: 'testuser@testorg.com' + schema: + type: string + - name: new_password + description: Password you want to set. If you do not provide this value then a random password will be generated. + required: false + multiline: false + example: "*******" + schema: + type: string + returns: + schema: + type: string + - name: get_user_devices + description: Get GWS user devices. + parameters: + - name: user_email + description: User email you want to reset password of. + required: true + multiline: false + example: 'testuser@testorg.com' + schema: + type: string + - name: customer_id + description: Customer ID of the account. Can be found in admin console under account -> account settings. + required: true + multiline: false + example: "C02dnh9vw" + schema: + type: string + returns: + schema: + type: string + - name: suspend_user + description: Suspend GWS user. + parameters: + - name: user_email + description: User email you want to reset password of. + required: true + multiline: false + example: 'testuser@testorg.com' + schema: + type: string + returns: + schema: + type: string + - name: reactivate_user + description: Reactivate the suspended GWS user. + parameters: + - name: service_account_file_id + description: Upload a service account file to Shuffle and use the file ID here. + example: "file_id" + required: true + schema: + type: string + - name: subject + description: User email associated with service account. + required: true + multiline: false + example: 'adminuser@testorg.com' + schema: + type: string + - name: user_email + description: User email you want to reset password of. + required: true + multiline: false + example: 'testuser@testorg.com' + schema: + type: string + returns: + schema: + type: string +large_image: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABAAAAAQACAMAAABIw9uxAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAMAUExURUxpce8+OjWlajqxbq6TADSoU/9gNDuRtOhGLupDNepDNepENTSoU+pDNTSoUzWnU+pDMupDNepDNepDNepDNepDNTSoUzSoUzOoUupDNTKqVTSoUzSoU+tDNepDNTSoUupDNTKnZUGI5kCJ4etDNTSoUzSoUzSoUzSoUjSoU+xEM/u8BTOpUzSpU+hFNupDNTSoU+xCNetDNTyaqOtCNOpDNepDNetFNOpDNTSoU+pCNTOpVOpDNOtDNTOoUzSpUzSoUzSoUzSoUzSoU+tFNDOoUzSoUzSoUzOoU+tCNTSoUz2QwTSrVDSoU/u7BUCK3zSoU+tCNOtDNTKnUelCNOpDNTSpUz6OyUCK2zSpU+pDNepDNetDNT+L1zanUupCNT2YrDyUsulDNTSoU+pCNfy8Bj+M1OpDNexDM/u8BTSpUzOoUz2RuTSoUupDNfu8BPu8BD6PxT6OzD2QvzuWrupDNO5EM/u8BTuVrOpDNfu8BTOoU/u8BP+9APu7BTSpU/qxCu1XLOlEND2WpDyVrTyTr/m7Bvq8Bfu8Bfy8Bfq9BT6OxupCND6Ozvu8BjySt/++APy9BTSpU/u8Bfu8BPq8BepGNPu8BOpDNOtDNPu9Bfu8BetCNDupUP+7BO9pJvu8BOpDNepDNPy8BT+N0DSoUzyUpUaqS/+2ADSoU/u8Bfu8BDuWqfm7Bu1QL/y8BupDNfu8BjyUsvSJGf+7ADyTtfi+B+pCNTqYqvu9BT2RvfmoDDuUqlisRN65EPy9BPigDz6pT/OCHfq+Bfq1CPu8BLG1IlasRs+4Fv+8CHmvOI6xL/J2Ici3GT2UrWiuPjSoU/y8BTuVqfWYFJSxLby2Hfu8BT2SuPWQFtq5EnywN/u8BDyUsz2TtKCzKO9qJqCzKHmvODmfeUyqSdK5FTyTtTelazajcjSoU+pDNUKF9Pu8BUGF8/u8BUGG8UGH7UGI6jWkaOu7CzSnV/S8CDSmXTmbkDiegT+Nzz2QwjehdTuVqT+M2TuYnj2TtTuXokN3Xn4AAADodFJOUwARBgMB/AOOCsH7zP7o9SEG+f7fQ/K86hX1EH/5Zn9E7gr+/HBmotLDKRn9OuUVtG8zvBce5ZjUKlx2M9ijeC3uVpvJ27XXzeBM3ccMrvT5qSWJGTeSh933kapdr/McVA9/LvLEV+5QDY0/c62Wyap30ePAabkP01eExUhuCfpR6sUiK2B0K6Rg2mfXPOa2nw+bTIBCMo6wYdG9zVjwGcE8a57e6o0j5wVh5uNFJ81R7IWIyxSYIkc46brgTej+S9jpwTbzRuvb+B3s5MP0PeZPljPS8fDspsr53DmMke643tK9wHjeHDUbygXtAAAz4UlEQVR42uzdsU5i2xoH8ENijEJMYMCCAkJBRSGBIBoCnY0lRhowITEkUNk5Ca01tRkTG30Cu3km3+LmnnvPuefOjI4o4N57/X6PwP7+rL3Wt9baf/wBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQGDupVr/xp8KPzhs3rdTMTwTJsJ26aRSm36rl28eL5iTTzVeKz79XrBzm73YveoN5pzQuNPopPyTEZ4jvL6YP5cHFpLv/vBrpvaPdYXteHRWOd/y+EEWpy9OHeW/3pPi8Vk939XbZPwFExMH5qPzY7K459z/bm/Tm3wqtbU8APmN+f1MonV3c7T9/rly+2a5+7/sfgA3JXo7Lw27uOUpy3Yuz0uLAw4F1Rv9+PuymnyNrr3n7rWF5AFbt66L6eJd7joMvJ8Py+MYjg5VM9o9Hg0nlOW6Kk8Ho2NIAfGTc/16u7z3H11N9Pq15jLC0nUK5fvicBHv1csG6ALzZwfR2UnxOkuLu2VSPAH6rNWp3n5Pp5HHU8oDhBdlFJ9Yz/jfNB4alY08aftQvDZMe/r902/emA/C/1/5wwv+XfHv81YOH1Lidfw5SOnO2sFGAoN/7q83ic8gOH+/dN0KYS36noQ79P+wczpQbqoGw1Er1ouz/Y0XgNKsoCMRNtZkT+h/7g49T/wEk3tZi4MX/pe2C9ZL2IAm2XZD+33QGJlX/ASQz/Ytb6X+DXH2kMUDS3vzPB4ey/fb/gHvHB0nSvF/6l38P8B9AEhyfefN/35rgUG+QmDt4mHwR5fdvFLx1dJDYyo7raSH+oMyDJUHiOPEv9PbFdyV7hZuWA4iZVrkruatT6RW2FBUxsX06tNV35VOB0kxlEYfBf67ntxb7g0vVRbTtlDKSuj5HVa8BRNfNoCKka34NaLs8gIjO/C80/TbRFLg+dZEYUZPq2O+3MSdmAkTKuWX/DR8V6FkQJCrv/uOJRG7eZGwmQBTW/Y+E8XN0S1n1x6eqzZ8E8fPsdRwU4BOn/nVH/T59McB5QT7F1qmpfyTagvWFYkT8A9YsKEg2uvJX1fWPlLuRlgCbi7/jPpFz5C+AjUiduegjmi2Bsv2BrNuso+8XWYcdfwEY/QO2f2ZjAOIfsCcTAdbz8l/28h+PicBDVrWyYl+N/jF6C+j4C2CVsiWNv1g50RRkZbbFP366YxeJs5r42/UXS1c2CPNhW2PH/WOr6dogPubyWoxiLN2uqWHerfbont+YK85tC+Cdnb+Bmz6T0BOsagjwjrW/qn0/SWkInCpnljT2dd8krQa6N4xl9OtCkyi5M0sBvNXszOQ/cfZKlgIw+Q95KeC74ua3Fr7vnVj1lvrmVa2hm/6TvCugnFXjvChbLgpJsp2MlTkvvf3fCUjyDc0D+JVU29t/GPOAjn4AP6/9V0QjmH6Ag8L8v2Mf+grJl57Lg/nH4p+dP6HZGyl7/uv7iUDYFECgZhb/wlQpuTWQP8Z7ohCqK4cEQ1dz7C9kubOsDISspPcXuLuGFASrZfgn7SUgUFuGf/7t6FwYAnRwofT5z0pA2d5gs39CfgmwEhDY8D9U9HgJCNWp3j8/uOrLRSBmPeXOT4pVGwODsPCxX37p2umA5NsuO/jHC/Z8Qijp+u785RU93w9JtGpRjfOavF1ByZXS/ON30hqCVv8IWdNaYDJX/9Jqm7eo3ItL4hxcK2ze6MttVmKSpWDvH0u4Mg1IkuzAtX8sZd80IDlquwqaZfV2JMfrP+G6czzI6j+mAcTZzOYf3t8NsCko5ho2//ABGd2AWLvfV8N8xKGzATHu/rUVMB+U7ghSTB3o/rECQ/3AeE7/D9Uuq9D1CcEYmpr+syJPBXmKW/ff9J8V9gPLLgyNlZSzf1gICFa/q2JZrd0DuYqLU9N/Vs6OgLh4sPmfNciNZMvyHwEvBZ45GhB5O776zfqWArMSFm2tO1XK+tw5HBRpiyc1ylqXAhtSFl3TogplvSp2BUZW2dWfrF26KmnRXP4fKE42oa0ZYPmfgF37hnDkzJrqkg3JpAQuau0/u//ZlF0Hg6Km4ep/5D9Y353+Qf61/8H8PzhV7X+M/8E6U5XIf6i2bP9B/sPd/tdTlch/qLK2/yH/tv+B/IeX/11Vyabo/0XNwZ2qxPgfqprt/8i//IP8B+cmryqR/1D15R/5D9ax47/If7Au5R/5D3f8P1SVbIr+f+Ty7+sfGP/N/0H+w+v/ef9H/uUf5D84Lf1/5D9YtRNVifwHm3/7/5H/YKUyqpJN0f+PmtmVqsT4H6qs+7+Q/2Btu/8T+Q/Wlvu/kf9w+f4H8h+ujqpE/oNV8v1P5D9Y9/KP/AdrUVSWbIj9P5Fzua8sMf6HquUAMPIfrNSRskT+Q5XdVZbIf6hsAEb+A9ZWlsi/DYAg/8GZ2gDEhuj/R895UV1i/A92A4AvgCD/wfpqAwDyH24D8FpdIv/BulWXyH+wHtQl8h+sQlphIv+hOnYCGPkP1kwDgM2w/0cDAOM/UXKmMJH/YLkCFPkPV6OoMpH/UB3kVSbyH6rsRGUi/8HyDUDkP1wjlckm6P9bAMT4T6SkLAAi/8HaqitN5D9Y7gBG/sO1cAQY+Q9WzR2gyH+wtnfVJvIfrLnaRP6DVXAEkPWz/yeqCwBPihPjvwUAkH87AED+g9HIqc4NKlby+cxV82+ZTKabzz9VKsl+DOb/UTXrCuV67d1d986q38aFRr82e+VJ7LQuF9NRtTzo1Sf5nPyzEY8SuqbcX10MquNFLfu+hZna+bR6O0zIP4H8R9a9pK5Y+uR60Jlezla1RFsrlG7r+bT8swY3vgK0wuh3L+b3jexaHlT2eNp5vKrIPystqyuxXYlcpv1wvv517q3+6HZSlH9WZC66H/bl6LG0pmH/hUnB5bd2Jif/fNi5M8AfU6mXC18/593tvHrxJP98qAPoErCPDPztce2TH2Bt3I76I7T/J8LaYvzeFl/v201EHuJxqXdo/OcdvjsD+B7FZqexFa0n2a82c/LPclKHwry0/OB7NN9pZ+PHJ/lnCRfivGSrr166ifQTPe40I7Wqa/4fZT4DtNyL/8UoDsNZ6yE6/wHG/yjr2wK4xJrfoJCNz9RuVM8Z/3nd1rVYv3Xs751mY/Z0a9XJF+M/rygJdlLT//d7QNr4z0tDREW23zKKPRzEeZY3PzT+80u+A/iGjl+nFffHvF0Y5oz//MQlAL+TvphmE/GoW+VD4z8mAEs5KiWpghvtovGffxiK+GuDf/10O2n/+OU94z9/mQr5y57mrSQ+8+zoTv7504HvAL3oapxN7HNv9DbSF/T+H3WuAX5J83Qr0U++Pyga/4PnQ6AvTP17l8l/+KnOofyHLes7IL/c8df+F3t391pVdsZxHDuHUxMJnJNjLnKRYFuv5kBEaxwJyV1upJBiMExRi1Mx1UCJ6YsjIzFY28mL08lMMDYmFsepUIIhNVYnTsaCMuqFMBRkYCi0xevpks6/0HHGaF7Oy1777LPWetb6fv6FZz2/s/c+61nrbCD1v9hJ/4esmWZfb0t3SzgrIH2gnf4P1m4uAlyndWtg361Se6pzGjzf/9wvfQ39vrb9mzIBLoTN/fR/iDgFJPRf/xc/BUc38fwfnA11tPyqXT9tmXAXQ+poO/0fGLYArPr01xb4M2vqUHK3CvD8L+HFjy0ALzW0HWNFZJrq6P9gpKdp+2X1HTtZEM+09GR5/g9EG32/bNtulsOynbtq+f0PIus5BeC5zkOshpW21/D7zxfAgF7+MyyGNV8DD9TR/777A18An6ntYbkWsLE5y/O/318AN9H8XzuynaVQ2N4T9L/PLtL8X3650JRmJRT9iWha4Pnf3y+AHAPE0385b/bU8vvvqR76v30zy6CcfdP8/ntpd23o7d/YnGEZlJfR+hhI/0sR/FWgR/ayCKI5vIn+986e0D/+HUixCKJKNUW8OZ73fzHPdYGfA3jwVdaAjrM76H+vhH0XeEMTP/+a0lsbef73x7HWkPu/hrG/GPbW0P/eeCvkj//8/Mf8ErA1y/O/H3Y2htv/R85S/7h2T9P/Xgh3CrC+O0P5K3h17OD53wPbg50CzO2j+pU5usDvv3jB7gHa1kLxK3W2n99/4W6H+vXvALVPQLoty++/ZKn+MPu/k7n/hGyu4/dfsENh9n8HR34npmUHv/9yn+CC3AScbaLySS6ibvpfqiA3Aed4/E/Ynxrpf5EyuQD7v4bRn8Rtz/H+L1GAV4HUN3PsXxUcO8jvv8CqhXcQYMNRyl4VqbZ6+l+aZl7/kZg9PP8LsyG4u8B2sPkPWNYdWv938/oPLGsJbAyYf/+BcB8Attym5ECoDwC53ZQceCmsvwCmOfkPWGFDQ0j9v43hH2Clgd8G1P/7MxQcWOH9LvVpMB8Bmjn5F1jlvFLqXhj3AdRvpdzA2geArz0I4TWgkd3/wNovAOob+SdZ3/u/YTPVBlY7vaSe+7zO7/5fYPoHWOuceuHMX33u/1a2/wBrpa++DAA19EE9/Q8EpFet8k9fx4I72f4HrJO6tDoA1JnXvOz/1zj8D1hvUq019IGP2/85/QMoYEatd9O7bYHt/P4DBVxQhdzp9Oz5n99/oJDjBQNAdXm1LbCd/gcKeTSkivBoOijH93+goD5V1L0c/Q947dsxoCIe+LEtcIH9P0Bhg6qU/GdZ+h/wVnpMlSZ/OqhhH2UGCrurypE+HZRl/hcoZqZsAKi86OmgWs7/AIoZVlHcFHxiMOd/AUVNRAoAwdNBzdQYKOZWV7QAEDsd1EONgaIGVGSfSnwNOMj1v0BR6bnoASBxOujIRmoMFHVX6RC3LTDHABBQwpTSI2s6aAsbAIESFvOaASBqOqj2NhUGSrihtAm6O4gNAEAps9f0A0DOdNAuCgyU0qti+XxBxB8AGQoMlDITLwBETAd1bqC+QCnz+ZgBIGA6qPEw9QVK6lPxOX53UD0TgECZT4BfVBAA6s60ywHwFuUFqvIJ8MV0kMP/B55gAgAoY1xVyNlDAnJ8AATKeJSvNABcnQ7Kbqe6QBkDqnJu3h30IcUFykiNqSQ4OB3UQXGBcq6oZNxr5QMAIM5EQgHg2nQQO4CA8k53JRUAjk0H8QEAKG9UJcihu4MOUlqgvJkkA8Cd6SA+AAARXM4nGgBq6IkT00FZLgEEIhhQSXNiWyAjAEAUlxIPABfuDupnBACIYFhVgfW7gxo5BBiI4ryqCsuvARwCCkSRulqdALA7HbSDFwAgivuqWizeHdS4l8ICUfSp6rE2HdREXYEo0teqGAC27g46kaKwQBSTqqqsTAc17KSuQCQT1Q0AK9NBzAAB0VR2GnC0Q8PreAEA3PRQVZ/hbYH8AwBE9dhAABi+O6iNqgLRpJeUEQbvDppmCxAQ0RVliLHXgHqGgIGo+kwFgLHpoF0UFYgoNafMuWliW+BCC1UFIrqgTDIxHXSRogJR3TAaAAbuDupnCwAQ2ZgyrMrTQfVcBAhENqyMq+500J+pKRDZgPkAUGeq+BqwhS+AQHSnLASAyn9Wyx5AwL5beWVFte4Oas9QUyCyUWVJle4OOkpJgeiO2wqA6kwH1VBRILrZLmVP8tNBDAEAOiaVTYlPB+2nooCGPqsBoIaS/T8wyzEggI4xZVmidwf1UFBAw7yyLsHpoAb2AAE6Bu0HQILTQdwFDmgZVy5IaDpo4U0KCmg4PeREACQ0HcQmYEDLpHJEEleI1m2koICOG64EQBJ3B22lnoCWS8odlU4HtWaoJ6DD1iRgVaaDeAAA9PQqp1Q0HcQDAKBpQjmmgukgHgAATVddC4D42wIXjlFOQMuick/cu4OaKSeg55xyUazpoIYNlBPQc9zJAFD3YrwG/JFqAnpSS24GQIzpoOxOygnomVfO0p0O6qCagBefAJ6/BrRqBcBhqgloGnE4ANQDndeAExQT0DXncgBoTQfdppiApkXluMjTQe1cBw7oGnU9ACJPBzVRTEDXhPMBEHE6aIGDQABtY0qAKNsCu6kloOuyEqH83UG1bAICtF2XEQCq60mZANhGLQFtfUqKMq8Be6gloO2UmAAofUhALk0tAV2zQ3ICoOR0EAcBAPruK1GKTgdlX6WWgLZBWQFQdDroIKUE9I0IC4Bi00F8AgRiGJMWAIWng1r5BAjoc+tKkPjTQewCBGK4qyRaNx1Uv5dSAvrOiwyAddNB/VQSiGFcCbV6W+CHVBKI4QupAaDuTK/YBMBtAEAMj5RcK+4OYg4IiOO6kuzFa8ABKgnEcF50ACxPBzVyFBAQx5TsAHg+HbSfQgJxzCnpnm0LPEQhgRhuKfnu5RoyVBKIYdKDAFBnuBEYiGXAhwBQvRQSiGPEh/7vOk0hv/FdyPOx1SVz0ocAmKL1v/VfyPNrmyvm9JAPATBK6xMAYv3c5oq54EP/D92i9QkAsb56xeKKGfUhAGbofAJAsL9bXDE3fAiAQTqfABDsPYsrZsqHAFik8wkAwd6xuGLGPOj/MRqfAJDsJ/YWzGzegwDoo/EJAMme/tDaghn24Q1gksYnAER729qC6fVhG+AsjU8AiPaJtQVz3oMAGKfvCQDZ3rW2YI7zJyABANt+YG3B+DAJMEzfEwDC/c7Sekl7MAmwlKLvCQDhfmZpvSx68AAwQtsTAHwFjMeH44CYBCQAxPuVpfVyjn3ABADC/QrYJ7//r9L1BIB4T/9iZ71M8QmAAIADLO0F9OBfwHN0PQEg3y+sLJdUl/wAmKfrCQD5XreyXC7L7/9rND0B4AE7E8FX5AfAcZqeAPDA/75jY7l4cCDgAE1PAPjAyrmAHhwIeIWmJwB88G8by0X+rUBDXAlEAHjByu0gM+ID4BI9TwB4wcpm4KviA+AxPU8A8DdATB4MAzMJRAB48jfAG2wD4DAQAoC/AQy6L/880DQ9TwD44T/mV8t18QFwipYnAPgbIK5BvgESAAj3bwD5pwEwCkgA+OKn5leL/DPB79PyBIAnvjK/Wk5J7/88+wAJAG+YPxp8TnoAcC0wAeCP37APSNcUHU8AeOM904vllvhPADfoeALAGz8yvVjmxQdALx1PAPA/YFzybwW5QMcTAN4wPg7Uy58ABADc+R/wFTYC6uFOEAKA/wHjE38g2DgNTwB45PeGF8sEkwAEANzxN8OLRfy9YIM0PAHgEdO3A4nfCXyXhicA2AgQm/gTATkOiABgI0B80i8G5F9AAsArhgeCZ6U/AMzR7wSAT75vdq2IPxKU88AIAK88/Z7RtSJ+FIB7QQkAv/zD6FoRfyZwH/1OAHjF7IkAD9kGQADAJWZPBhc/C3SdficA2AkU2zmGgQkAuMTs1QAD0gPgMv1OAHjlHaNrRfowYJ5rwQgAv7xrdK08Fh4AS7Q7AeAXs1sBR4QHwEnanQDwy4+NrpVx4QEwQ7sTAH4xeznQjPAAGKHdCQDPvGFyrVxiIyABAKd8bHKtnBQeAAO0OwHgGaOnAko/D4SrwQkA37xtcq0sCQ8ArgUiAHxj9FhQ6QcCPaTdCQDPfGRyrUi/G/hftDsB4BmT00DiTwSbp90JAM98YnCpvM8sEAEAt5g8GFz8kYCcCUwA+OaXBpfKovRhQLqdAPDN6waXyrDwAOii2wkA3/yfvfN7rfLKwrBE49yEzF1TOoHBhE47pIKQq1JonUppaVpTGJikFykJxYAzKZofELE2vTA2atDoWBEMKYIZFWuRihYrTkWGttqiRalKay0tMxebL4djEP+AmSZ1qHpST3JO9t7v2s/zJ6xvvS/r23uttX3OA+9mGhgDgLjoxACKZj9qxwCs4XMhgPpWcNYBYADmaPWYKvvEDWAUtWMA1mjGAIqGh8EwAAygBN5lIRAGAHFRjwFgABhAuvh8H1j9ZbDtqB0DwADmzhkMAAOAuJjEAIpmD2rHAKzhcy3wUQwAA4C4yGMARbMGtWMA1shhABgABoABYAD8AmAA/AJwCIgBYAAYAAbANSAGwC0AjUAYAAaAAZQLWoExAEi4EQgDwAAgYQNgHBgDgMjwOQtwnYUgGACkawDqK8G2oHYMAAOYOywFxQAgMuoxgKJhLTgGgAGUAA+DYAAQGT53AvI0GAYACRsAj4NiABAZPt8F4HlwDAAiw+fLQIfUDeAD5I4BGMPn24ALBsQN4DpyxwCMMe4zV1aKG8AZ5I4BGMPn8+ALDosbwCrkjgEYo8VnruwXN4CtyB0DMMawz1zpFzeAtcgdAzBGt89cGRU3gNXIHQMwRpPPXNkrbgBDyB0DMEavz1zZzkogDACi4pTPXBkSN4B+5I4BGKPLZ65cEjeAq8gdAzDGNZ+58qH6OOAh9I4B2GLEZ66sZRoIA4CoOOYzV7aqG8C/0DsGYItNPnNllboBHEXvGIAtBn3mivrbYBMb0DsGYIsjPnNFfS84rYAYgDU2+syVD9QNYA16xwBs0e4zV46rGwCPg2EAtsh7zRX5nWCH0TsGYIpJv8mivhKogb3AGIAp6v0mi/pGkIndCB4DsESr32RZTyMABgAR0ek3WfaoG8BaBI8BWGLcb7LsVDeAnQgeA7BEi99k2aVuAKwEwQBM0e03WTaoG8B+BI8BWKLXb7LITwM17EDxGIAhrvlNln3qBsBAMAZgiht+k0V+GGBiM4rHAAyxyW+yyD8QzjwgBmCKdX6TZZH6+8AT21E8BmCIjZ6z5X2uATAAiIbcEs/ZIt8LzDUABmCISd/ZMiR/CLAPyWMAZmj2nS2r5Q2AtYAYgB3afGeLfCsg0wAYgCHGfWfLUXkDGEXyGIAZhn1ny255AxjgeTAMwAxNvrNFfi0oS4EwAEN0+c6WxfKdQDQDYwB2+M57uuznFBADgFjo854ue+UNoB/NYwBWOOk9XfQ7gQZYDY4BGCHX7j1ddumfAr6L6DEAG9T5T5fN+gawFtFjADZo9Z8u+juBeCEUA7DCuP90eU/fAD5ehOoxABMMB8iXlfoO8A6qxwBM0BsgX0b1DWArqscATDASIF/W6BvAEKrHAEzQESBfPtQ3gMOoHgMwwboA+bJV3wAmPkL2GIAFGgPky7sGDIB5IAzAApMh8uW8AQOgEwADoA9ojhgYCKYTAAMwQU+QhOk3UAKwFAQDMEBTkIQxcA/IOAAGYIFrQRLGwDzgxF50jwHQBjA3VhkwAHYCYAAGOB0kYd4xYAATnyJ8DECdAOtAfuKQgWuAiUsIHwNQpy5Qxli4Bngf4WMA6rQFyhgL1wCMBGMAtAEkfA3ARSAGQBtAwtcAE+tRPgYgzkigjNltwQAGjiN9DECbvkAZ85aFawAmAjEAdQ6EShkL1wAT25E+BiDNZLCUGTLxD7AD7WMA3ALOhQ0WDGBiFdrHAJRpCZYyFpYCNXz9b7SPAShzKljK7GiQ1/+tr1z1EsSPAQhzLFzO7FfX/ycHnXN/QvwYgDCnw+XMHnH9f1/7P/27lxE/BqBLvj1czmg3A9/+wk1R+zDqxwBkqQ+YM2fky/8pHkX9GIAs4wFzRvmJ4Ctjd/TvHkL9GIAs3SGT5qp6+T9F1WvIHwNQZSRk0mxXvf37p/sly5A/BqDKYMikET0FPDd2l/7dY8g/aU4I6z/XGDJyn2o2/1W5e3gKEaTMsLABNAeNnGIv4D3l/xRvIIKUaRY2gJ6wodsiX/5PsZx24IQ5kBM2gN6wsduppv+vK10hnkQG6dLBJMCc2Sp8+/dLnkMG6dKkbABHwsZOay/gNwdn0L+r+i06SJZOYf3fXBg2dodWCun/s1o3I39BB6nSflPYANpCR2+9fvk/RU0FSkiUQeU/gOHQ0VutfPv3S9gKkCpdygbQFTp6R0X0f2Hs1/XPRFCy9CgbQF/o6B1v0Cj/q9yDeAYpJEnFJI3ApdBvoPyf4lW0wBEAjcCz5pKB8n+K6kcQQ4qcUjaAlvDxi/6J0AKzP4X5G2JIkU7OAEsi9q1At75yRfLCQtSQHo15zgBLI+7d4OfGXNG8jRzS45iy/vONEUQw6nmg72uL1797HDmkR7eyAbTGEMHNqs1/3ASC9i6AGM4AFyz4SGDxd5G8gh5S42LGGWCpHI518Xf1LPXvKpkJTI1rGWeApbImztu/L6vcrKEZKDW2Kes/1xhFDKM8BCiq+e/+ZiBeCUuLxpucAZbMednmP9YCpM4N6T+A7kiiGN9m0Bk2/z2YmpcQRUpITwJmJyKJ4iUT5f80TyMK/gBUuBhJGCPbCXBubO76d8spARJCug0wq4sljDsGTJT/lADJ0SJtAD3RxDGixYDFz/5wCpA87dp/AF3RBDKeN0K/OetKhRIgGaRfBAn8LvBd7Itm8Xd1yfp3NTwTxh+AAjfjSdRIXgeY7ewPJUDifwCT0gbQFlEot0vO/szA67QDpoF2F1DWFFEoY3gi8Eq1KxP/QBtJ0KltAB0RhfJ88OXgc5r9mWki4M+IIwEu56T1n9sYUzBDLwcvpfnvfp5FHQlwSrsAaI0qmB8KN//dTy17ARKgXtsAuqMK5vWwi78rXXl5GXmYp09b/9mxqKK5+KqV8n+a3yMQ6wxr6z93IK5wDoUr/8+WX//ued4IMI74IGBkRwABHwj6rNrNB7wWbpwTGUcA5STQRODtr9z8sJSGYNu0ihtAR2wB3avc/EdDcGoMius/3xhbRNdKN/8V6AbiKtAyPeIG0BZdRD9Qnf3hlZAEuZwXN4Cm+GLab6f8n+ZNdGKWpowjgHLjeSvIhbF51r97jKtAq7TXcQRQdt7xqv8vK928swylGGVEvQDojDGqHp8HmI/mvwKLAV5DKiZZqH4HmJ2KMaz+BoLOjTkvMBJgk03q+s/WxRjW3b70/32t8wTngCbZpq7/ujiPp/ZrLP6eBS+wI9wgg/IFQEucgV0tsvib7WBp0yNvADfiDKyP7eCfVfvUv6t9Br1Y43ROXf+xjQLfYfHHsrM/MzcDVKAYY7TIFwBtsYZ2tXrzXwF+h2JscUS+AMh6Y43tdVPlPz8BFABx0hdtcOezF+j2F1UuBCvoCLbExby8/ifj/S2dx16gW5+7QCxDNYbo1i8AeuKN7vzNBF84G0r/bAawxGX9AiA7EXF8R+fr3Z9KF47nuQngBIBJwKJYa6z85ybAFoP6VwBxTgLeYV4eCTx3Nqz+XeUTSMcGnfr6z7qijvB65dmfGXmRmQATdBjQf+5k1CEu+0Pht791EfAq4jFARbMBA2iNO8bvlfl9gG8Ouih4G/noM2JA//G2Af7MHv3mv0IsZzuQPI11FgxgXeRRPmqu/J/mcRoC1em1oP/62KN86LCZ2z+eCjLFxpsWDKA7+jiXbSTwwlhM+ne1T6EhaVos6D/bFH2cy7UePGjzX8G7wEcQkTAdJvRfJ9CVOmqv/J/mIY4BhE8A600YwLBAqDfYK/9pCeYEkD+AYjk+YK/8n6aKNeGqnM7zB+CNNQbL/5+7AZgM1mRhZ8YfgDfO2Gj+K9gNwGSwJCcy/gD8UVorQDTNfwV5AzEpngDWZfwBeKSEl8L9L/6eJY8iJz1ajBQA3SLxPj/nY8BPDkauf1f7V/SkxncZfwB+2WOy/J+mhrEgMTZOGtF/nUwjyqdz2/z3bZUTgINAMbZl/AF4ZtEWU7d/9/CHRYhKiBsZfwDemcNy0CtjTgU6AoU4edOK/uuFWtFn3Q3YEGnzX+GOwCfRlQpWWoAEdgHdxZDR8p+rADGumdF/7ohS3PdpLf6eLUvpCdbgiJkfgHgfBS9M/yz0/0OtU+PFhxGXAI3NZvQf+XsA97HBTvNfQVYsQV7x02NH//mNWqF/62qxsz9nnSTPsR4kekbs6D8bVwv+ajvNf8wFabIub8gAfqMW/aIGAkSa/wqzDIlxAOCLyXa5+K+xMPvzq+0ATAZyAMAqkBl58E3glRonTeXfURkHAH7oE/wCo5qb/2ZBNQ1B0TJo6QAga1Y8ct5sqflvBgd4AqXFyZFJUwVAl+I3+NXVYBfOOgvU/BGtcQBIE0Bhdlku/+84wDOoLT4qxk3pP+vR/AzvDVgu/6kB4qXblv6VNgHcxU4jsz84gBbXjOm/WbXr9KMGI7M/OIASm/LGDOCU7Kco0Ax0+0dnjRcYDo6Ji3XG9J+7LPstdku9+0MNYIGT9cb0n20T/hp77539ed1ZhLuAaDjQak3/WYfw57h7QXjDj1XOJkupAeKgvdOc/uuk99CPGr39wwHQP0eARbBKcvH3HFhOV3B4KrbZ03/+gPQnWbzl/6P/lc401W8iwMAsbLGn/6xF/KNssF/+33EApoMD02tQ/9mg+Ec59L6h2Z8H7AdgQwj6Lzdt8p/lpxLgh2qXAlXLUCH6Ly8n5L/LW4dvf+5S4Vl2BYeiyaT+69r1v8x/Drp0eIXHw9F/Gek18GkqliZkAG4Fbwah//LdAV628HGeTskA3GOvIUf0n/YmkHt4qSYpB1j6FILk/K889Nn4PmmVAK72SSTpkyUtVvXfZuUL/Ze983lpM1vjuCS6C2aXSBIIUZJGiBBwJYIxCTEhNSgIGV0oLYILHRftzKLgwkXbVRkK/g12QOiqMDAM/RsGuZu7mdncVRBRh3I7vTOde29bO63WRN8k57zve87z+fwHec/5npzzfJ8fsq4ArVB8EFm6xuS+rfo/+sWWNRJ2BcAMcJGHq9bqf8GaXRSLSjsBIpgB7vDzgrX6N3MaQHvy0g6A1gTVgW7waNRe/Z9N2rNOgZq4EyBBZYB+7p7aq/+j2zatVLoljxXygjXz64nF+rcjCejTFaAu8ATILqFRnXvq3pHNPLFrtYoCD4BWlCYh+vh+32r9Hz+3a7mCtySeAENbZAToCv8vW63/o33bFmynJRL8QMJ/IjsBXaUs8wSoUxqgga+PLdf/hn1rlpR5ALQSPANUk3pyZDuPLVy2itAToFXhGaCU58vW63/VxnUbH5J6AkRpGKyQH06t17/R48A6U5J6ALRCTaqDFDH5lf3yP1q189U4si72BGjNEQtUwoMFAfo/emHp6sXlHgCtIS4BKqL/JxL0v2xrEvn8hOAToJUNI2Cu/0741doVTEs+AFrraQzBfrg7KkP/y/ZWkclMCL6QF8gloPe//3vHMvRvwTSgzmzKPgBaw3EiAb3xeEyI/G2+ALwjIvwEaGXHEXP33BHz92+vBXBOYajFJQBBd8mjBTHytzUH4BMrLfHMbSLprv7+vzmRo39LkwCxAi9XBzA/zDkH/xEkfzurAC6vJ/pvtaJpGgY6Y63RevWnnACALdPArmEwi/7fvwNoF+aAQHz4/cf6/UyK/vcFLOr0EPL/8A4gKeAmdv7uJfvbWxn6P34gYVlLiP88M3ALP+A6ljKhT9/q1X9FHADfiVjYkVnEf059F5l3IhW/XDz6lwAr4PiljLVNI/2/qWIJtn/8z1z5l/jD/lzAJ0JWV3pJwEVCi4QC2jz+2+2Qf9vuB54+lLK+xAEvMJThCLjMZqcO0n/Z7Qd+I2eJiQNeyg5u0jb0M+ELsb8vsdoPHJ2Us8jkA35xBKxwBJwzvhi67kP99sbeA+CfktZ5CtFfZjYeQ/0D4VLihu9krx+4LMsUrqD5K7cA6dOEC5WQg+/02tK24HeFnfXDSJ4j4FJk2JH8rfUDN6StdxzBt3kIiA0HFjLOnSEb/cDjR9JWPDCH3tvdArYFmoKDe112irKvPvC+QLc3hNzb5gUsFoT9FaS7/y/43bIOwac/Crz1kQzQiciunHYBI/FoL5/IMj/wJ4nPvhjJAJ3LhLZkuIKFUq/B4Fd/WqT/hTsiAz+7CP26YID1/YNTB+V+vpBFfuCLAZlk0Pl1VNMpixc/udivE2xNm5B9ofrnEXCjLbhiqScQm1FhAlniB548F5v8UUTjN3kCjUP7UkRzK6p6wry2oU3I7QG5LCLxm68BpZxd6ld57/vDfD9QUhXgFZZoD+aEct6SDMG1rariT2O+H/h0QDKUBTqNCOaNLxRYmtGR/Wn62IAN2SMiBikLdEqiMjVv7kKH85GEpg9jdJuQk5cDslmKIm3nIcGskfeAwF6ppvOzmOwH3h6QDo+ALu8BB2bFA1KHJe1nvLltQsYmxR8AOAHd3wNmTKkXKmxV3Gn8YKofeBf9kw7UC7XtHb+nCS5NZVxcWTPbhNxH/u9IUhjc00Wg2kz6NUkoVizVXf4cJqYFnj1E/e/ZRs09Es0c+C5ZeK34LJvw4mOYNzbga7R/HiVmVFAfrEdmcj65CcT24pF1776EaX7gahDtn5NjVFB/zFbiSW87CAQLUyvlhMefwSw/UF4fwM7QIrR/QnOl9LQXV4HAdHq77I8uz0b5gaQAXNhDZQSsJk2gWkrnXPMHguGdfOaWr65v5rQJWb6D7j8TpipIoT8w12hO6b0MrG2mnzXmEj788ab4gcffovqL0BpA+TFQr2xv7RaU1g8El3LFrZVG1c9jXQzxA++h+cvQJFiXVVhurGwVc2t93AhGxjeL+WYmW0sY8YtN8AMXyAH+ghSTQnRbBXPZxnY8f3C4WVi7/l4QWwpP55LFqfTMs0zk1oRxHo3/xwbwALhKgXGBbjI8W6tXy5HPlKvVav0dUQvWwfdtQp6g96ukUSWo8gP9PTaAIsC2NNi4oMwP9HN9IEWA7Z+edfYtKHsGvMUBMC4lOMG+Bev9wGUeAJ3Is23B9mfAyQOE3hGmhYFCfDk24CdkTjYAuPQM8J8fuEER8HWMr7NrQR2+Gxtw+jMipygA3MNnbUKeIvEboEEYKMVXfiBtQG8OA9AgDNQGAvzjB47SBvRm1hgWBGrxix94/Bh5O+CQPuGgFp+0CaELmDNm2LFg4TNgNYC2HTHIuDBQjfdtQs5+RNoOmScQCKrx3A/8BWE7JkwgEFTjcZsQmoB0Q5JZIaAaT8cG0AacQCB4jXdjA05founuAoH0BwL1eOYHMgi060AghYGgHo/8wK8QNIFA8AVe+IEEAHphmkbhoAH3xwacPUfNvVAkJxg04LYfeEwX4B5hbDjowOWxAZQA9GwF0CMQtOCmH7hPE7CeCWTZq6DlGeBam5Cx79Fx74zU2KugA7f8wJNHqBgrAHzI/47JADKAXawA0IMbYwMoAeobpgaDrmeAdj9wI4WAMQPBr+geG0AAUIkZWGKngia0tgk5ZQygEoJUBoIuNPqBxy/QrhpSZTYq6HoGaGsT8g3KVZYOQG0waEPT2ID7ZACqY3yWfQq60OIHLk8iW4VskhAE2tCQFjhGE3C1JBPsU9CG6jYhGADqUwJpFAz6UOsH0gKAlEAwC6VtQqgAICUQDEOhH3gPsWrhGbsUNKKqTch3GIAkBYOBqBkbsHoHqepKCmZuMOhEhR849g+Eqi8pOMsmBZ30XR+IAaiVWJU9Cjrpc2zA6beIVO8JQGEQaKUvP/CEBABOADCbfsYGPEWgvALAdHr2A6kAdoORW2xR0PsMeEsCECcAyKUnP5AOAK6dADQIAd3PgK7bhGyQAOQaazV2KOil2/rAZVoAu0i4zg4Fzc+AN+ifOADIpZuxAcsP0SQnAEh9BlAAwAkAFuLQD6QDIDmBYOczwEmbkNHnqJETAOzkZj8Q/Xt2AmTZn6Cbm9qEnFEAzB0AbA4EvKEA2K/MR9ifoJ1rxgacPUKFXhKgSxjop6MfyP3fa4J0CgXPngHo33sGm2xP0E5bPxD9+4KZEPsTtHO1TQj69wlpTgDQz5d+4Cj69wsHTA4F/VyuD+T/30fsDrM9QT8X/MCxl8jOR0xH2Z2gn09jAxao//EXtAgBN/joB1L/7ztGsuxO0M+HsQGr9P/xH6kKuxNc4PXpxiRy82NS4DabE1zgX3cQm09TgticoJ3FAErzK3kSAkAzJeZ/kBAAYmkOojI/U6ixR0EbiQMkhh0IUhneQ2C+J5Bho4IWotPIywAGqQ8GHdTGEZcZFAkFgnKqSyjLFDapDQLFZEfQlTmE59ixoJIM6T+EAkEqQ3kkZVwokKxAUMT6DoIyj51Zdi4oCf8XUJORgQAmiIMCyoT/DWW+we6FfmmkUJKpBJvkBEF/PKP6z+icoHW2MPTO8BQaMjwQwAhx6Jk64T/zMwJW2MfQGxGy/2xgitIA6IUmz387GMcPBJ7/gonhB0KXRDfRDc8A4PkPVpCrsanBKSHcf+uYpz4QnF7/k+jFQopUB4ETsmuIxUpICgIH13/cP2sJxKkNgOuZPUQnFrPLMwCu4xatf+1mhCni0Pn6H6f1n+0E4wk2OrS//hfRhwDGq2x1IPovORZIw1D4kqE40X8x5JgbAJeZIPdfEjHyAuEiETp/CiM5wa6Hj6xT+ivQEKRGGM4pY/6LZIdLALRaifwgWhAaCSiRGiyeKn//ki8BNRQg3Pwj9w87AMSaf1T+kxNAToDYv/8mY79gYH6bSIBI6uT+wAcKdAoRGPyf4fUPHwmmmSEozftn6hdcYGkRTQhiOE/lD1xmKoou+PsHuaSaVAmLIDpF6h+0dQQZIiiADIV/0IFB3gG2cyvHNofOxJq0DLSYdYJ/cAPj9A22lgZd/8DBO4AyYSuZoOkvOPMD4kwTt/D2T+Yf8A6QSiXMroYumM4iGoti/9T9QLfs1RGOHUTTxP6BUIBQQmT+QI+QFWA+i/T8g94pRJCQ0Y//HfYw9EWOI8BY6kXKfqBvkjQMMpJZnH9QQnAKQ8A4EtvE/kDZEVDkCDCKoRJp/6CSQJoKAYPkT94fqGY+Pou0jCBCzT9wC0D+AKpvATO0DPK58X/ILgVuAfz7A3AEyKKC/MENUvkaavPfvz+TfsEtgntkB/qK0CKzPsBNBveyyM438m9MsyPBbQoZRgn5geEV0n7AE8IrtAzxmol8jI0IXrHUJDHAU/lvzbMJwUsCB1V06FXWzwEFv+A90yVeAu4zlCHyBz5hZIbkIHeZjVPuDz4ilWa2uHvUtoj8AS8Boa5/JUmrP/DnS4DGQdrv/tvk/AHXAKF//pH/t3c3K41DUQCAB53ZSbPLlEYILhojVMg2FPonbaVYKAiFbtx0Z+kTuOg79B268G18pIHZzDAyjI5Vm+T7niH35Nxzzr23r+7PYbuLFATf6ee/8POnAI63Uy8K7V2an/m0KIibPDyxZvdn0nHLL8VyEV1buHtRG7Q870vxHG2Gzgu+fd43q/uUKKh6ltoKvMH3hXlfCh4DmmLA/65+qT9lEGRODL7WMtlq+VMWX1sL0wGvGfhpGvanXI5bO22Bl/iWZnp+lHQvoB7wr8y/79+PGFDNWd9ka9qPCsSA0Kjw85p/srH6qYijeD6y5n8V/dLOhY8CiUAlt/2z3KwflVR/nPWqvfovk76Lval0ItCcLSta85s1vekDX87i+bhirYHTsNM25wu/JQJVeWTo5Hyt2Q/P3PYXZc8EamEU6/bB39zlg7I2CE/T+YOKH/xLfbNLS3a58OV01XK6D15eFMgXaSnmBHrDKLblh9drxNGwyKXBk1GS+fHDW9y0smR8WrwN/yK/sPZhP7nAfZacFyQKLK8Gq3tJP+zZUbcfTUcHfNNwbZx0toGnO+F9w8BsdGDZwGU4WD0FZvvgYxwHcXM+Pf/0YwS98SzK29r78CnqrTxKwtGHjw3URsP1atO21YeDCATdbXOepJN3jgS1STrbZf3WrY0+HKKzoLVpRutpOtrb9mA5uRomu9XjQ9fNHVCcOkEjaMebxyzaJdMwHU8mvZfkB8ve9WQcTpP1vNPMn+67t7r5UJoEoREE7Z/iP7W7wV3DagcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMrjB5u8iKa7CD0yAAAAAElFTkSuQmCC diff --git a/gws/1.0.0/requirements.txt b/gws/1.0.0/requirements.txt new file mode 100644 index 00000000..102c8fc3 --- /dev/null +++ b/gws/1.0.0/requirements.txt @@ -0,0 +1,6 @@ +requests==2.25.1 +google-auth==1.28.0 +google-auth-oauthlib==0.4.3 +google-auth-httplib2==0.0.4 +google-api-python-client==2.0.2 + diff --git a/gws/1.0.0/src/app.py b/gws/1.0.0/src/app.py new file mode 100644 index 00000000..211055c3 --- /dev/null +++ b/gws/1.0.0/src/app.py @@ -0,0 +1,130 @@ +import socket +import asyncio +import time +import random +import json +import requests +import secrets +import string + +from walkoff_app_sdk.app_base import AppBase + +from google.oauth2 import service_account +from googleapiclient.discovery import build + + +class Gws(AppBase): + __version__ = "1.0.0" + app_name = "Google Workspace" + + def __init__(self, redis, logger, console_logger=None): + """ + Each app should have this __init__ to set up Redis and logging. + :param redis: + :param logger: + :param console_logger: + """ + super().__init__(redis, logger, console_logger) + + def reset_user_password(self, service_account_file_id, subject ,user_email,new_password): + service_account_file = self.get_file(service_account_file_id) + service_account_info = service_account_file['data'].decode() + + def generate_secure_password(length=12): + characters = string.ascii_letters + string.digits + string.punctuation + secure_password = ''.join(secrets.choice(characters) for i in range(length)) + return secure_password + + if new_password == "": + print("Generating new password") + new_password = generate_secure_password() + + try: + service_account_info = json.loads(service_account_info) + except Exception as e: + print(f"Error loading service account file: {e}") + return {"success": False, "message": f"Error loading service account file: {e}"} + + SCOPES = ['https://www.googleapis.com/auth/admin.directory.user'] + + creds = service_account.Credentials.from_service_account_info(service_account_info, scopes=SCOPES,subject=subject) + service = build('admin', 'directory_v1', credentials=creds) + + try: + result = service.users().update(userKey=user_email, body={'password': new_password}).execute() + return {"success": True, "message": f"Password for {user_email} reset successfully.", "new_password": new_password} + except Exception as e: + return {"success": False, "message": f"Error resetting password: {e}"} + + def get_user_devices(self, service_account_file_id, subject ,user_email, customer_id): + service_account_file = self.get_file(service_account_file_id) + service_account_info = service_account_file['data'].decode() + + try: + service_account_info = json.loads(service_account_info) + except Exception as e: + print(f"Error loading service account file: {e}") + return {"success": False, "message": f"Error loading service account file: {e}"} + + SCOPES = ['https://www.googleapis.com/auth/admin.directory.device.mobile'] + + creds = service_account.Credentials.from_service_account_info(service_account_info, scopes=SCOPES,subject=subject) + service = build('admin', 'directory_v1', credentials=creds) + + query = f'email:{user_email}' + + try: + results = service.mobiledevices().list(customerId=customer_id, query=query).execute() + devices = results.get('mobiledevices', []) + except Exception as e: + return {"success": False, "message": f"Error getting devices: {e}"} + + return {"success": True, "message": f"Devices for {user_email} retrieved successfully.", "devices": devices} + + def suspend_user(self, service_account_file_id, subject ,user_email): + service_account_file = self.get_file(service_account_file_id) + service_account_info = service_account_file['data'].decode() + + try: + service_account_info = json.loads(service_account_info) + except Exception as e: + print(f"Error loading service account file: {e}") + return {"success": False, "message": f"Error loading service account file: {e}"} + + SCOPES = ['https://www.googleapis.com/auth/admin.directory.user'] + + creds = service_account.Credentials.from_service_account_info(service_account_info, scopes=SCOPES,subject=subject) + service = build('admin', 'directory_v1', credentials=creds) + + try: + result = service.users().update(userKey=user_email,body={'suspended': True}).execute() + except Exception as e: + return {"success": False, "message": f"Error suspending user: {e}"} + + return {"success": True, "message": f"{user_email} suspended successfully."} + + def reactivate_user(self, service_account_file_id, subject ,user_email): + service_account_file = self.get_file(service_account_file_id) + service_account_info = service_account_file['data'].decode() + + try: + service_account_info = json.loads(service_account_info) + except Exception as e: + print(f"Error loading service account file: {e}") + return {"success": False, "message": f"Error loading service account file: {e}"} + + SCOPES = ['https://www.googleapis.com/auth/admin.directory.user'] + + creds = service_account.Credentials.from_service_account_info(service_account_info, scopes=SCOPES,subject=subject) + service = build('admin', 'directory_v1', credentials=creds) + + try: + result = service.users().update(userKey=user_email,body={'suspended': False}).execute() + except Exception as e: + return {"success": False, "message": f"Error reactivating user: {e}"} + + return {"success": True, "message": f"{user_email} reactivated successfully."} + + +if __name__ == "__main__": + Gws.run() From 70f563964ec16bb2ea32f23288499f9ecf6fb05f Mon Sep 17 00:00:00 2001 From: Dhaval Dave Date: Mon, 29 Apr 2024 12:24:26 +0530 Subject: [PATCH 109/259] added docs for gws app --- gws/1.0.0/README.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 gws/1.0.0/README.md diff --git a/gws/1.0.0/README.md b/gws/1.0.0/README.md new file mode 100644 index 00000000..ceaa74b1 --- /dev/null +++ b/gws/1.0.0/README.md @@ -0,0 +1,41 @@ +## Google Workspace +An app for interacting with Google Workspace or GWS. +## Requirements +1) Enable the Admin SDK API from GCP console. + - Login to Google cloud (Make sure you are using the same administrator acount that you're using for Google Workspace) and In the navigation menu on the left-hand side, click on “APIs & Services” > “Library”. + - In the API Library, use the search bar to find the "Admin SDK". Click on it to open the API page. + - Click the “Enable” button to activate the Admin SDK API for your project. + 2) Create a Service account. + - Go to the navigation menu, and select “IAM & Admin” > “Service Accounts”. + - Click on “Create Service Account” at the top of the page. + - Enter a service account name and description, then click “Create”. + - You can skip the permission part here as we will be adding persmissions from GWS console later on. + - In the service account details page, click on “Keys”. + - Click on “Add Key” and select “Create new key”. + - Choose “JSON” as the key type and click “Create”. This will download the JSON key file which contains the “client_id”. Note down this client ID. + + 3) Subject (Email address associated with the service account) + - Note down the email address associated with the service account you just created it'll be used in the authentication in Shuffle. + 4) Adding permissions to the service account from GWS console. + - Signin to the Google Workspace admin console. + - In the Admin console, locate the sidebar and navigate to Security > API controls. This area allows you to manage third-party and internal application access to your Google Workspace data. + - Under the Domain-wide delegation section, click on “Manage Domain Wide Delegation” to view and configure client access. + - If the service account client ID is not listed, you will add it; if it is already listed but you need to update permissions, click on the service account’s client ID. + - To add a new client ID: + - Click on Add new. + - Enter the Client ID of the service account you noted earlier when creating the service account in GCP. + - In the OAuth Scopes field, enter the scopes required for your service account to function correctly. OAuth Scopes specify the permissions that your application requests. + - Depending on the actions you want to use below are the OAuth scopes required. + +| Action | OAuth Scope | +|---------------------|---------------------------------------------------------------------------------------------------------------------------------------------| +| Reset User Password | `https://www.googleapis.com/auth/admin.directory.user` | +| Suspend User | `https://www.googleapis.com/auth/admin.directory.user` | +| Get User Devices |`https://www.googleapis.com/auth/admin.directory.device.mobile` | +| Reactivate User | `https://www.googleapis.com/auth/admin.directory.user` + +## Authentication +1) Upload the Service account JSON file in to the Shuffle files and copy the file id. +2) Now, Inside the GWS app authentication in Shuffle; use the file id you just copied and in subject use the email address asscoitate with your service account. + + From 4b6822688e01f8b84846de7960195047b56f17ec Mon Sep 17 00:00:00 2001 From: Dhaval Dave Date: Mon, 29 Apr 2024 12:30:14 +0530 Subject: [PATCH 110/259] fixed gws app doc --- gws/1.0.0/README.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/gws/1.0.0/README.md b/gws/1.0.0/README.md index ceaa74b1..5c8eb2e4 100644 --- a/gws/1.0.0/README.md +++ b/gws/1.0.0/README.md @@ -18,13 +18,12 @@ An app for interacting with Google Workspace or GWS. - Note down the email address associated with the service account you just created it'll be used in the authentication in Shuffle. 4) Adding permissions to the service account from GWS console. - Signin to the Google Workspace admin console. - - In the Admin console, locate the sidebar and navigate to Security > API controls. This area allows you to manage third-party and internal application access to your Google Workspace data. - - Under the Domain-wide delegation section, click on “Manage Domain Wide Delegation” to view and configure client access. - - If the service account client ID is not listed, you will add it; if it is already listed but you need to update permissions, click on the service account’s client ID. - - To add a new client ID: - - Click on Add new. - - Enter the Client ID of the service account you noted earlier when creating the service account in GCP. - - In the OAuth Scopes field, enter the scopes required for your service account to function correctly. OAuth Scopes specify the permissions that your application requests. + - In the Admin console, locate the sidebar and navigate to Security > API controls. This area allows you to manage third-party and internal application access to your Google Workspace data. + - Under the Domain-wide delegation section, click on “Manage Domain Wide Delegation” to view and configure client access. + - If the service account client ID is not listed, you will add it; if it is already listed but you need to update permissions, click on the service account’s client ID. To add a new client ID: + - Click on Add new. + - Enter the Client ID of the service account you noted earlier when creating the service account in GCP. + - In the OAuth Scopes field, enter the scopes required for your service account to function correctly. OAuth Scopes specify the permissions that your application requests. - Depending on the actions you want to use below are the OAuth scopes required. | Action | OAuth Scope | From 8112147ef9231a6ad47199af748db2b092e928f1 Mon Sep 17 00:00:00 2001 From: Frikky Date: Sun, 5 May 2024 18:42:40 +0200 Subject: [PATCH 111/259] Added dict merging to branches for shuffle tools --- shuffle-tools/1.2.0/api.yaml | 3 ++- shuffle-tools/1.2.0/src/app.py | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/shuffle-tools/1.2.0/api.yaml b/shuffle-tools/1.2.0/api.yaml index a31a363d..81e4a7e7 100644 --- a/shuffle-tools/1.2.0/api.yaml +++ b/shuffle-tools/1.2.0/api.yaml @@ -282,7 +282,7 @@ actions: required: false multiline: false example: "domains,urls,email_addresses,ipv4s,ipv4_cidrs,ipv6s,md5s,sha256s,sha1s,cves" - value: "domains,urls,ipv4s,md5s,sha1s" + value: "domains,urls,ipv4s,md5s,sha1s,email_addresses" multiselect: true schema: type: string @@ -1106,6 +1106,7 @@ actions: example: 'list' options: - list + - dict schema: type: string - name: run_ssh_command diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 45160b86..04c854d3 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -2642,6 +2642,15 @@ def merge_incoming_branches(self, input_type="list"): #newlist.append(item) results = newlist + elif input_type == "dict": + new_dict = {} + for item in results: + if not isinstance(item, dict): + continue + + new_dict = self.merge_lists(new_dict, item) + + results = json.dumps(new_dict) else: return { "success": False, From 941d5c723dfba836544bc9ea40d955ad8280c863 Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 8 May 2024 23:08:50 +0200 Subject: [PATCH 112/259] Rebuild with new SDK --- shuffle-tools/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/shuffle-tools/README.md b/shuffle-tools/README.md index 0a3518ac..febda00c 100644 --- a/shuffle-tools/README.md +++ b/shuffle-tools/README.md @@ -1,7 +1,6 @@ # Shuffle tools App documentation Shuffle tools is a utility app that simplifies your understanding of what happens in a node and also allows you to test on the fly. - ## Actions The Shuffle-tools app comes with a multitude of different actions, here we will check a few out and give a brief description. From 08133f32f8c62fee13091dc38c1de3d00e8ad06f Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 9 May 2024 13:52:22 +0200 Subject: [PATCH 113/259] Fixed routes to us /api/v1/ for mail/sms --- email/1.3.0/api.yaml | 27 +++++++++++++++++++++++++++ email/1.3.0/src/app.py | 16 ++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/email/1.3.0/api.yaml b/email/1.3.0/api.yaml index 12679e9d..23fdff8f 100644 --- a/email/1.3.0/api.yaml +++ b/email/1.3.0/api.yaml @@ -303,4 +303,31 @@ actions: returns: schema: type: string + - name: send_sms_shuffle + description: Send an SMS from Shuffle + parameters: + - name: apikey + description: Your https://shuffler.io organization apikey + multiline: false + example: "https://shuffler.io apikey" + required: true + schema: + type: string + - name: phone_numbers + description: The receivers of the SMS + multiline: false + example: "+4741323535,+8151023022" + required: true + schema: + type: string + - name: body + description: The SMS to add to the numbers + multiline: true + example: "This is an alert from Shuffle :)" + required: true + schema: + type: string + returns: + schema: + type: string large_image: data:image/png;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/hAytodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6Nzg4QTJBMjVEMDI1MTFFN0EwQUVDODc5QjYyQkFCMUQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6Nzg4QTJBMjZEMDI1MTFFN0EwQUVDODc5QjYyQkFCMUQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3ODhBMkEyM0QwMjUxMUU3QTBBRUM4NzlCNjJCQUIxRCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3ODhBMkEyNEQwMjUxMUU3QTBBRUM4NzlCNjJCQUIxRCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pv/bAEMAAwICAgICAwICAgMDAwMEBgQEBAQECAYGBQYJCAoKCQgJCQoM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 diff --git a/email/1.3.0/src/app.py b/email/1.3.0/src/app.py index 0b7feb45..ed3992cd 100644 --- a/email/1.3.0/src/app.py +++ b/email/1.3.0/src/app.py @@ -633,6 +633,22 @@ def analyze_headers(self, headers): # Should be a dictionary return analyzed_headers + # This is an SMS function of Shuffle + def send_sms_shuffle(self, apikey, phone_numbers, body): + phone_numbers = self.parse_list_internal(phone_numbers) + + targets = [phone_numbers] + if ", " in phone_numbers: + targets = phone_numbers.split(", ") + elif "," in phone_numbers: + targets = phone_numbers.split(",") + + data = {"numbers": targets, "body": body} + + url = "https://shuffler.io/api/v1/functions/sendsms" + headers = {"Authorization": "Bearer %s" % apikey} + return requests.post(url, headers=headers, json=data, verify=False).text + # Run the actual thing after we've checked params def run(request): From 36333b01ef79a03dce2d5a0482e8d436aa4823b5 Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 9 May 2024 20:29:56 +0200 Subject: [PATCH 114/259] Fixed base64 decoding to have more failure handlers --- shuffle-tools/1.2.0/src/app.py | 68 +++++++++++++++++----------------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 04c854d3..a312551d 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -94,48 +94,46 @@ def base64_conversion(self, string, operation): return value elif operation == "decode": + decoded_bytes = "" + + # For loop this. It's stupid. try: decoded_bytes = base64.b64decode(string) - try: - decoded_bytes = str(decoded_bytes, "utf-8") - except: - pass + except Exception as e: + if "incorrect padding" in str(e).lower(): + try: + decoded_bytes = base64.b64decode(string + "=") + except Exception as e: + if "incorrect padding" in str(e).lower(): + try: + decoded_bytes = base64.b64decode(string + "==") + except Exception as e: + if "incorrect padding" in str(e).lower(): + try: + decoded_bytes = base64.b64decode(string + "===") + except Exception as e: + if "incorrect padding" in str(e).lower(): + return "Invalid Base64" - # Check if json - try: - decoded_bytes = json.loads(decoded_bytes) - except: - pass - return decoded_bytes - except Exception as e: - #return string.decode("utf-16") + decoded_bytes = base64.b64decode(string) + try: + decoded_bytes = str(decoded_bytes, "utf-8") + except: + pass - return { - "success": False, - "reason": f"Error decoding the base64: {e}", - } - #newvar = binascii.a2b_base64(string) - #try: - # if str(newvar).startswith("b'") and str(newvar).endswith("'"): - # newvar = newvar[2:-1] - #except Exception as e: - #return newvar - - #try: - # return newvar - #except: - # pass + # Check if json + try: + decoded_bytes = json.loads(decoded_bytes) + except: + pass - return { - "success": False, - "reason": "Error decoding the base64", - } + return decoded_bytes - return json.dumps({ + return { "success": False, - "reason": "No base64 to be converted", - }) + "reason": "Invalid operation", + } def parse_list_internal(self, input_list): if isinstance(input_list, list): @@ -565,7 +563,7 @@ def check_wildcard(self, wildcardstring, matching_string): if wildcardstring in str(matching_string).lower(): return True else: - wildcardstring = wildcardstring.replace(".", "\.") + wildcardstring = wildcardstring.replace(".", "\\.") wildcardstring = wildcardstring.replace("*", ".*") if re.match(wildcardstring, str(matching_string).lower()): From 284ce193f59ea2f167c27eb1127bccf3d984f4a7 Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 13 May 2024 04:17:55 +0200 Subject: [PATCH 115/259] Bumped shuffle AI app --- email/1.3.0/src/app.py | 21 ++++++++++++--- shuffle-ai/1.0.0/upload.sh | 4 +-- shuffle-tools/1.2.0/src/app.py | 47 ++++++++++++++++++++++------------ 3 files changed, 50 insertions(+), 22 deletions(-) diff --git a/email/1.3.0/src/app.py b/email/1.3.0/src/app.py index ed3992cd..51406b65 100644 --- a/email/1.3.0/src/app.py +++ b/email/1.3.0/src/app.py @@ -395,6 +395,10 @@ def parse_eml(self, filedata, extract_attachments=False): "data": filedata, } + # Encode the data as utf-8 if it's not base64 + if not str(parsedfile["data"]).endswith("="): + parsedfile["data"] = parsedfile["data"].encode("utf-8") + return self.parse_email_file(parsedfile, extract_attachments) def parse_email_file(self, file_id, extract_attachments=False): @@ -413,8 +417,6 @@ def parse_email_file(self, file_id, extract_attachments=False): "reason": "Couldn't get file with ID %s" % file_id } - #print("PRE: ", file_path) - # Check if data is in base64 and decode it # If it ends with = then it may be bas64 @@ -434,6 +436,16 @@ def parse_email_file(self, file_id, extract_attachments=False): else: extract_attachments = False + # Replace raw newlines \\r\\n with actual newlines + # The data is a byte string, so we need to decode it to utf-8 + try: + print("Pre size: %d" % len(file_path["data"])) + file_path["data"] = file_path["data"].decode("utf-8").replace("\\r\\n", "\n").encode("utf-8") + print("Post size: %d" % len(file_path["data"])) + except Exception as e: + print(f"Failed to decode file: {e}") + pass + # Makes msg into eml if ".msg" in file_path["filename"] or "." not in file_path["filename"]: print(f"[DEBUG] Working with .msg file {file_path['filename']}. Filesize: {len(file_path['data'])}") @@ -448,6 +460,7 @@ def parse_email_file(self, file_id, extract_attachments=False): if ".msg" in file_path["filename"]: return {"success":False, "reason":f"Exception occured during msg parsing: {e}"} + ep = eml_parser.EmlParser( include_attachment_data=True, include_raw_body=True @@ -456,8 +469,8 @@ def parse_email_file(self, file_id, extract_attachments=False): try: print("Pre email") parsed_eml = ep.decode_email_bytes(file_path['data']) - if str(parsed_eml["header"]["date"]) == "1970-01-01 00:00:00+00:00" and len(parsed_eml["header"]["subject"]) == 0: - return {"success":False,"reason":"Not a valid EML/MSG file, or the file have a timestamp or subject defined (required).", "date": str(parsed_eml["header"]["date"]), "subject": str(parsed_eml["header"]["subject"])} + #if str(parsed_eml["header"]["date"]) == "1970-01-01 00:00:00+00:00" and len(parsed_eml["header"]["subject"]) == 0: + # return {"success":False,"reason":"Not a valid EML/MSG file, or the file have a timestamp or subject defined (required).", "date": str(parsed_eml["header"]["date"]), "subject": str(parsed_eml["header"]["subject"])} # Put attachments in the shuffle file system print("Pre attachment") diff --git a/shuffle-ai/1.0.0/upload.sh b/shuffle-ai/1.0.0/upload.sh index 33f84bac..6dbdff4d 100755 --- a/shuffle-ai/1.0.0/upload.sh +++ b/shuffle-ai/1.0.0/upload.sh @@ -1,6 +1,6 @@ gcloud run deploy shuffle-ai-1-0-0 \ --region=europe-west2 \ - --max-instances=3 \ + --max-instances=5 \ --set-env-vars=SHUFFLE_APP_EXPOSED_PORT=8080,SHUFFLE_SWARM_CONFIG=run,SHUFFLE_LOGS_DISABLED=true --source=./ \ - --timeout=1800s + --timeout=300s diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index a312551d..c0c8d6b5 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -94,29 +94,44 @@ def base64_conversion(self, string, operation): return value elif operation == "decode": - decoded_bytes = "" + + if "-" in string: + string = string.replace("-", "+", -1) + + if "_" in string: + string = string.replace("_", "/", -1) + + # Fix padding + if len(string) % 4 != 0: + string += "=" * (4 - len(string) % 4) + # For loop this. It's stupid. + decoded_bytes = "" try: decoded_bytes = base64.b64decode(string) except Exception as e: - if "incorrect padding" in str(e).lower(): - try: - decoded_bytes = base64.b64decode(string + "=") - except Exception as e: - if "incorrect padding" in str(e).lower(): - try: - decoded_bytes = base64.b64decode(string + "==") - except Exception as e: - if "incorrect padding" in str(e).lower(): - try: - decoded_bytes = base64.b64decode(string + "===") - except Exception as e: - if "incorrect padding" in str(e).lower(): - return "Invalid Base64" + return json.dumps({ + "success": False, + "reason": "Invalid Base64 - %s" % e, + }) + + #if "incorrect padding" in str(e).lower(): + # try: + # decoded_bytes = base64.b64decode(string + "=") + # except Exception as e: + # if "incorrect padding" in str(e).lower(): + # try: + # decoded_bytes = base64.b64decode(string + "==") + # except Exception as e: + # if "incorrect padding" in str(e).lower(): + # try: + # decoded_bytes = base64.b64decode(string + "===") + # except Exception as e: + # if "incorrect padding" in str(e).lower(): + # return "Invalid Base64" - decoded_bytes = base64.b64decode(string) try: decoded_bytes = str(decoded_bytes, "utf-8") except: From acc90cd1fb4d233ef02445b54356973ee3657c9f Mon Sep 17 00:00:00 2001 From: Frikky Date: Sat, 18 May 2024 12:57:06 +0200 Subject: [PATCH 116/259] Updated readme to rebuild all --- shuffle-tools/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/shuffle-tools/README.md b/shuffle-tools/README.md index febda00c..0a3518ac 100644 --- a/shuffle-tools/README.md +++ b/shuffle-tools/README.md @@ -1,6 +1,7 @@ # Shuffle tools App documentation Shuffle tools is a utility app that simplifies your understanding of what happens in a node and also allows you to test on the fly. + ## Actions The Shuffle-tools app comes with a multitude of different actions, here we will check a few out and give a brief description. From 5f0d61463f6432d5b004070b6966863a6760f4a3 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Wed, 22 May 2024 01:43:54 +0530 Subject: [PATCH 117/259] feat[auth-overrides]: added in the support --- shuffle-subflow/1.1.0/src/app.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/shuffle-subflow/1.1.0/src/app.py b/shuffle-subflow/1.1.0/src/app.py index 5c4b4d8a..3ce1692e 100644 --- a/shuffle-subflow/1.1.0/src/app.py +++ b/shuffle-subflow/1.1.0/src/app.py @@ -26,7 +26,7 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" headers = { "Authorization": "Bearer %s" % user_apikey, - "User-Agent": "Shuffle Userinput 1.1.0" + "User-Agent": "Shuffle Userinput 1.1.0", } result = { @@ -143,7 +143,7 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" return json.dumps(result) - def run_subflow(self, user_apikey, workflow, argument, source_workflow="", source_execution="", source_node="", source_auth="", startnode="", backend_url="", check_result=""): + def run_subflow(self, user_apikey, workflow, argument, source_workflow="", source_execution="", source_node="", source_auth="", startnode="", backend_url="", check_result="", auth_override=""): #print("STARTNODE: %s" % startnode) url = "%s/api/v1/workflows/%s/execute" % (self.url, workflow) if len(self.base_url) > 0: @@ -190,6 +190,10 @@ def run_subflow(self, user_apikey, workflow, argument, source_workflow="", sourc "User-Agent": "Shuffle Subflow 1.1.0" } + if len(auth_override) > 0: + print("Overriding auth with: %s" % auth_override) + headers["appauth"] = auth_override + if len(str(argument)) == 0: ret = requests.post(url, headers=headers, params=params, verify=False, proxies=self.proxy_config) else: From ed793211dd6edabd54c227df52a0b2878f8cee3e Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Wed, 22 May 2024 01:45:43 +0530 Subject: [PATCH 118/259] feat[auth-overrides]: added in the support --- shuffle-subflow/1.0.0/src/app.py | 5 ++++- shuffle-subflow/1.1.0/src/app.py | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/shuffle-subflow/1.0.0/src/app.py b/shuffle-subflow/1.0.0/src/app.py index 82253179..c6f25c41 100644 --- a/shuffle-subflow/1.0.0/src/app.py +++ b/shuffle-subflow/1.0.0/src/app.py @@ -140,7 +140,7 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" return json.dumps(result) - def run_subflow(self, user_apikey, workflow, argument, source_workflow="", source_execution="", source_node="", source_auth="", startnode="", backend_url=""): + def run_subflow(self, user_apikey, workflow, argument, source_workflow="", source_execution="", source_node="", source_auth="", startnode="", backend_url="", auth_override=""): #print("STARTNODE: %s" % startnode) url = "%s/api/v1/workflows/%s/execute" % (self.url, workflow) if len(self.base_url) > 0: @@ -187,6 +187,9 @@ def run_subflow(self, user_apikey, workflow, argument, source_workflow="", sourc "User-Agent": "Shuffle Subflow 1.0.0" } + if len(auth_override) > 0: + headers["appauth"] = auth_override + if len(str(argument)) == 0: ret = requests.post(url, headers=headers, params=params) else: diff --git a/shuffle-subflow/1.1.0/src/app.py b/shuffle-subflow/1.1.0/src/app.py index 3ce1692e..4eead3fb 100644 --- a/shuffle-subflow/1.1.0/src/app.py +++ b/shuffle-subflow/1.1.0/src/app.py @@ -191,7 +191,6 @@ def run_subflow(self, user_apikey, workflow, argument, source_workflow="", sourc } if len(auth_override) > 0: - print("Overriding auth with: %s" % auth_override) headers["appauth"] = auth_override if len(str(argument)) == 0: From acb32e447266d6fe6d3ee7e847aa7d33146db9ef Mon Sep 17 00:00:00 2001 From: Frikky Date: Sat, 25 May 2024 21:10:46 +0200 Subject: [PATCH 119/259] Added more headers to be analyzed without lists --- email/1.3.0/src/app.py | 19 ++++++++++++++++++- shuffle-ai/1.0.0/src/app.py | 15 +++++++++++---- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/email/1.3.0/src/app.py b/email/1.3.0/src/app.py index 51406b65..4af23947 100644 --- a/email/1.3.0/src/app.py +++ b/email/1.3.0/src/app.py @@ -556,12 +556,16 @@ def analyze_headers(self, headers): analyzed_headers = { "success": True, + "sender": "", + "receiver": "", + "subject": "", + "date": "", "details": { "spf": "", "dkim": "", "dmarc": "", "spoofed": "", - } + }, } for item in headers: @@ -569,6 +573,19 @@ def analyze_headers(self, headers): item["key"] = item["name"] item["key"] = item["key"].lower() + + # Handle sender/receiver + if item["key"] == "from" or item["key"] == "sender" or item["key"] == "delivered-to": + analyzed_headers["sender"] = item["value"] + + if item["key"] == "to" or item["key"] == "receiver" or item["key"] == "delivered-to": + analyzed_headers["receiver"] = item["value"] + + if item["key"] == "subject" or item["key"] == "title": + analyzed_headers["subject"] = item["value"] + + if item["key"] == "date": + analyzed_headers["date"] = item["value"] if "spf" in item["key"]: analyzed_headers["details"]["spf"] = spf diff --git a/shuffle-ai/1.0.0/src/app.py b/shuffle-ai/1.0.0/src/app.py index 42e6e09c..9bcff4a2 100644 --- a/shuffle-ai/1.0.0/src/app.py +++ b/shuffle-ai/1.0.0/src/app.py @@ -215,6 +215,8 @@ def gpt(self, input_text): } def run_schemaless(self, category, action, app_name="", fields=""): + self.logger.info("[DEBUG] Running schemaless action with category '%s' and action label '%s'" % (category, action)) + """ action := shuffle.CategoryAction{ Label: step.Name, @@ -253,6 +255,12 @@ def run_schemaless(self, category, action, app_name="", fields=""): }) else: + fields = str(fields).strip() + if not fields.startswith("{") and not fields.startswith("["): + fields = json.dumps({ + "data": fields, + }) + try: loadedfields = json.loads(fields) for key, value in loadedfields.items(): @@ -262,11 +270,10 @@ def run_schemaless(self, category, action, app_name="", fields=""): }) except Exception as e: - print("[ERROR] Failed to load fields as JSON: %s" % e) + self.logger.info("[ERROR] Failed to load fields as JSON: %s" % e) return json.dumps({ "success": False, - "reason": "Ensure Fields are valid JSON", - "type": type(fields), + "reason": "Ensure 'Fields' are valid JSON", "details": "%s" % e, }) @@ -274,7 +281,7 @@ def run_schemaless(self, category, action, app_name="", fields=""): baseurl = "%s/api/v1/apps/categories/run" % self.base_url baseurl += "?execution_id=%s&authorization=%s" % (self.current_execution_id, self.authorization) - print("[DEBUG] Running schemaless action with URL '%s', category %s and action label %s" % (baseurl, category, action)) + self.logger.info("[DEBUG] Running schemaless action with URL '%s', category %s and action label %s" % (baseurl, category, action)) headers = {} request = requests.post( From 6190f892ba29e82807cb158de2b2ce82c37c8caf Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Mon, 27 May 2024 00:54:59 +0530 Subject: [PATCH 120/259] fix[debugging-email-app]: trying to make some adjustments to make email app work --- email/1.1.0/src/app.py | 2 +- email/1.2.0/src/app.py | 2 +- email/1.3.0/src/app.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/email/1.1.0/src/app.py b/email/1.1.0/src/app.py index 8b2868c8..6dbac7db 100644 --- a/email/1.1.0/src/app.py +++ b/email/1.1.0/src/app.py @@ -64,7 +64,7 @@ def send_email_shuffle(self, apikey, recipients, subject, body): elif "," in recipients: targets = recipients.split(",") - data = {"targets": targets, "body": body, "subject": subject, "type": "alert"} + data = {"targets": targets, "body": body, "subject": subject, "type": "alert", "email_app": True} url = "https://shuffler.io/functions/sendmail" headers = {"Authorization": "Bearer %s" % apikey} diff --git a/email/1.2.0/src/app.py b/email/1.2.0/src/app.py index 23993bf8..700c9890 100644 --- a/email/1.2.0/src/app.py +++ b/email/1.2.0/src/app.py @@ -67,7 +67,7 @@ def send_email_shuffle(self, apikey, recipients, subject, body): elif "," in recipients: targets = recipients.split(",") - data = {"targets": targets, "body": body, "subject": subject, "type": "alert"} + data = {"targets": targets, "body": body, "subject": subject, "type": "alert", "email_app": True} url = "https://shuffler.io/functions/sendmail" headers = {"Authorization": "Bearer %s" % apikey} diff --git a/email/1.3.0/src/app.py b/email/1.3.0/src/app.py index 51406b65..dbdc4d75 100644 --- a/email/1.3.0/src/app.py +++ b/email/1.3.0/src/app.py @@ -68,7 +68,7 @@ def send_email_shuffle(self, apikey, recipients, subject, body): elif "," in recipients: targets = recipients.split(",") - data = {"targets": targets, "body": body, "subject": subject, "type": "alert"} + data = {"targets": targets, "body": body, "subject": subject, "type": "alert", "email_app": True} url = "https://shuffler.io/functions/sendmail" headers = {"Authorization": "Bearer %s" % apikey} From 49ac10ef72305e2beb56281929c892307beef318 Mon Sep 17 00:00:00 2001 From: Frikky Date: Sun, 26 May 2024 22:13:38 +0200 Subject: [PATCH 121/259] Added analysis fixes to email app --- email/1.3.0/src/app.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/email/1.3.0/src/app.py b/email/1.3.0/src/app.py index 4af23947..ba5d67bc 100644 --- a/email/1.3.0/src/app.py +++ b/email/1.3.0/src/app.py @@ -518,6 +518,8 @@ def parse_email_headers(self, email_headers): # Basic function to check headers in an email # Can be dumped in in pretty much any format def analyze_headers(self, headers): + self.logger.info("Input headers: %s" % headers) + # Raw if isinstance(headers, str): headers = self.parse_email_headers(headers) @@ -531,6 +533,11 @@ def analyze_headers(self, headers): headers = headers["header"] if "header" in headers: headers = headers["header"] + + if "headers" in headers: + headers = headers["headers"] + if "headers" in headers: + headers = headers["headers"] if not isinstance(headers, list): newheaders = [] @@ -548,6 +555,7 @@ def analyze_headers(self, headers): headers = newheaders + #self.logger.info("Parsed headers: %s" % headers) spf = False dkim = False From 9495c5ca91eb142c3eeb268ea1cf6e1ba313e9c3 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Tue, 28 May 2024 23:06:07 +0530 Subject: [PATCH 122/259] fix[email-app-sms]: SMS bug fixing --- email/1.3.0/src/app.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/email/1.3.0/src/app.py b/email/1.3.0/src/app.py index 5e7dc58a..f3b414f6 100644 --- a/email/1.3.0/src/app.py +++ b/email/1.3.0/src/app.py @@ -665,13 +665,8 @@ def analyze_headers(self, headers): # This is an SMS function of Shuffle def send_sms_shuffle(self, apikey, phone_numbers, body): - phone_numbers = self.parse_list_internal(phone_numbers) - - targets = [phone_numbers] - if ", " in phone_numbers: - targets = phone_numbers.split(", ") - elif "," in phone_numbers: - targets = phone_numbers.split(",") + phone_numbers = phone_numbers.replace(" ", "") + targets = phone_numbers.split(",") data = {"numbers": targets, "body": body} From 0ddf2faff515a502ba37dd27b9d5d1ecaf74dc73 Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 6 Jun 2024 19:21:52 +0200 Subject: [PATCH 123/259] Fixed url vs urls in parse ioc --- shuffle-tools/1.2.0/src/app.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index c0c8d6b5..821d3830 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -2405,11 +2405,13 @@ def parse_ioc(self, input_string, input_type="all"): input_type = "all" else: input_type = input_type.split(",") - for item in input_type: + for i in range(len(input_type)): item = item.strip() if not item.endswith("s"): item = "%ss" % item + input_type[i] = item + ioc_types = input_type iocs = find_iocs(str(input_string), included_ioc_types=ioc_types) From 971ba03d2059c44d131c6ed40ec89d3c3ab0be78 Mon Sep 17 00:00:00 2001 From: Frikky Date: Tue, 11 Jun 2024 16:31:09 +0200 Subject: [PATCH 124/259] Run update apps with sdk --- shuffle-tools/1.2.0/api.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shuffle-tools/1.2.0/api.yaml b/shuffle-tools/1.2.0/api.yaml index 81e4a7e7..2c202eaa 100644 --- a/shuffle-tools/1.2.0/api.yaml +++ b/shuffle-tools/1.2.0/api.yaml @@ -1,7 +1,7 @@ --- app_version: 1.2.0 name: Shuffle Tools -description: A tool app for Shuffle. Gives access to most missing features along with Liquid. +description: A tool app for Shuffle. Gives access to most missing features along with Liquid. tags: - Testing - Shuffle From 8f5526bceae96d7170e3e9d29530dc3c59e9dcf1 Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 17 Jun 2024 00:56:28 +0200 Subject: [PATCH 125/259] Fixed shuffle-ai upload to not time out so fast --- shuffle-ai/1.0.0/src/app.py | 19 +++++++++++++++++++ shuffle-ai/1.0.0/upload.sh | 4 ++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/shuffle-ai/1.0.0/src/app.py b/shuffle-ai/1.0.0/src/app.py index 9bcff4a2..89925445 100644 --- a/shuffle-ai/1.0.0/src/app.py +++ b/shuffle-ai/1.0.0/src/app.py @@ -290,6 +290,25 @@ def run_schemaless(self, category, action, app_name="", fields=""): headers=headers, ) + try: + self.logger.info("Starting url checker") + if "parameters" in self.action: + response_headers = request.headers + for key, value in response_headers.items(): + if not str(key).lower().endswith("-url"): + continue + + self.action["parameters"].append({ + "name": key, + "value": value, + }) + + self.logger.info("[DEBUG] Response header: %s: %s" % (key, value)) + else: + self.logger.info("[DEBUG] No parameters in action. Can't append url headers.") + except Exception as e: + self.logger.info("[ERROR] Failed to get response headers: %s" % e) + try: return request.json() except: diff --git a/shuffle-ai/1.0.0/upload.sh b/shuffle-ai/1.0.0/upload.sh index 6dbdff4d..e5a55fb5 100755 --- a/shuffle-ai/1.0.0/upload.sh +++ b/shuffle-ai/1.0.0/upload.sh @@ -2,5 +2,5 @@ gcloud run deploy shuffle-ai-1-0-0 \ --region=europe-west2 \ --max-instances=5 \ - --set-env-vars=SHUFFLE_APP_EXPOSED_PORT=8080,SHUFFLE_SWARM_CONFIG=run,SHUFFLE_LOGS_DISABLED=true --source=./ \ - --timeout=300s + --set-env-vars=SHUFFLE_APP_EXPOSED_PORT=8080,SHUFFLE_SWARM_CONFIG=run,SHUFFLE_LOGS_DISABLED=true,SHUFFLE_APP_SDK_TIMEOUT=120 --source=./ \ + --timeout=120s From 9247bab81e10e974d6edbe7eb815c3d9541f3a27 Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 17 Jun 2024 14:08:57 +0200 Subject: [PATCH 126/259] Added a dedup+merge action that is easy to use --- shuffle-tools/1.2.0/api.yaml | 49 +++++++++++++++++++++++ shuffle-tools/1.2.0/src/app.py | 73 ++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) diff --git a/shuffle-tools/1.2.0/api.yaml b/shuffle-tools/1.2.0/api.yaml index 2c202eaa..4e8bf4f7 100644 --- a/shuffle-tools/1.2.0/api.yaml +++ b/shuffle-tools/1.2.0/api.yaml @@ -35,6 +35,55 @@ actions: example: print("hello world") schema: type: string + + - name: dedup_and_merge + description: Merges data from multiple workflows within a set timeframe. Returns action as SKIPPED if the data is a duplicate. Returns with a list of all data if the data at the end + parameters: + - name: key + description: The key to use for deduplication + required: true + multiline: false + example: "ticketname+username" + schema: + type: string + - name: value + description: The full value of the item + required: true + multiline: true + example: "1208301599081" + schema: + type: string + - name: timeout + description: The timeout before returning + required: true + options: + - 2 + - 3 + - 4 + - 5 + - 6 + - 7 + - 8 + - 9 + - 10 + - 15 + - 20 + - 25 + multiline: false + example: "1" + schema: + type: string + - name: set_skipped + description: Whether to set the action SKIPPED or not IF it matches another workflow in the same timeframe + required: true + options: + - true + - false + multiline: false + example: "true" + schema: + type: string + - name: check_cache_contains description: Checks Shuffle cache whether a user-provided key contains a value. Returns ALL the values previously appended. parameters: diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 821d3830..ae1e0f5c 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -221,6 +221,79 @@ def send_email_shuffle(self, apikey, recipients, subject, body, attachments=""): def repeat_back_to_me(self, call): return call + def dedup_and_merge(self, key, value, timeout, set_skipped=True): + timeout = int(timeout) + key = str(key) + + set_skipped = True + if str(set_skipped).lower() == "false": + set_skipped = False + else: + set_skipped = True + + cachekey = "dedup-%s" % (key) + response = { + "success": False, + "datastore_key": cachekey, + "info": "All keys from the last %d seconds with the key '%s' have been merged. The result was set to SKIPPED in all other actions." % (timeout, key), + "timeout": timeout, + "original_value": value, + "all_values": [], + } + + found_cache = self.get_cache(cachekey) + + if found_cache["success"] == True and len(found_cache["value"]) > 0: + if "value" in found_cache: + if not str(found_cache["value"]).startswith("["): + found_cache["value"] = [found_cache["value"]] + else: + try: + found_cache["value"] = json.loads(found_cache["value"]) + except Exception as e: + self.logger.info("[ERROR] Failed parsing JSON: %s" % e) + else: + found_cache["value"] = [] + + found_cache["value"].append(value) + if "created" in found_cache: + if found_cache["created"] + timeout + 3 < time.time(): + set_skipped = False + response["success"] = True + response["all_values"] = found_cache["value"] + + self.delete_cache(cachekey) + + return json.dumps(response) + else: + self.logger.info("Dedup-key is already handled in another workflow with timeout %d" % timeout) + + self.set_cache(cachekey, json.dumps(found_cache["value"])) + if set_skipped == True: + self.action_result["status"] = "SKIPPED" + self.action_result["result"] = json.dumps({ + "status": False, + "reason": "Dedup-key is already handled in another workflow with timeout %d" % timeout, + }) + + self.send_result(self.action_result, {"Authorization": "Bearer %s" % self.authorization}, "/api/v1/streams") + + return found_cache + + parsedvalue = [value] + resp = self.set_cache(cachekey, json.dumps(parsedvalue)) + + self.logger.info("Sleeping for %d seconds while waiting for cache to fill up elsewhere" % timeout) + time.sleep(timeout) + found_cache = self.get_cache(cachekey) + + response["success"] = True + response["all_values"] = found_cache["value"] + + self.delete_cache(cachekey) + return json.dumps(response) + + # https://github.com/fhightower/ioc-finder def parse_file_ioc(self, file_ids, input_type="all"): def parse(data): From d919784208d2882d24eaf725bba45783a458a09e Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 19 Jun 2024 10:18:51 +0200 Subject: [PATCH 127/259] Small api fixes for shuffle tools/ai --- shuffle-ai/1.0.0/src/app.py | 9 ++-- shuffle-tools/1.2.0/api.yaml | 82 +++++++++++++++++----------------- shuffle-tools/1.2.0/src/app.py | 5 ++- 3 files changed, 48 insertions(+), 48 deletions(-) diff --git a/shuffle-ai/1.0.0/src/app.py b/shuffle-ai/1.0.0/src/app.py index 89925445..979a0158 100644 --- a/shuffle-ai/1.0.0/src/app.py +++ b/shuffle-ai/1.0.0/src/app.py @@ -243,6 +243,8 @@ def run_schemaless(self, category, action, app_name="", fields=""): if app_name: data["app_name"] = app_name + self.logger.info(f"\n\nFIELDS MAPPED\n\n: {fields}") + if fields: if isinstance(fields, list): data["fields"] = fields @@ -291,7 +293,6 @@ def run_schemaless(self, category, action, app_name="", fields=""): ) try: - self.logger.info("Starting url checker") if "parameters" in self.action: response_headers = request.headers for key, value in response_headers.items(): @@ -303,11 +304,9 @@ def run_schemaless(self, category, action, app_name="", fields=""): "value": value, }) - self.logger.info("[DEBUG] Response header: %s: %s" % (key, value)) - else: - self.logger.info("[DEBUG] No parameters in action. Can't append url headers.") + #self.logger.info("[DEBUG] Response header: %s: %s" % (key, value)) except Exception as e: - self.logger.info("[ERROR] Failed to get response headers: %s" % e) + self.logger.info("[ERROR] Failed to get response headers (category action url debug mapping): %s" % e) try: return request.json() diff --git a/shuffle-tools/1.2.0/api.yaml b/shuffle-tools/1.2.0/api.yaml index 4e8bf4f7..14f4f258 100644 --- a/shuffle-tools/1.2.0/api.yaml +++ b/shuffle-tools/1.2.0/api.yaml @@ -184,47 +184,47 @@ actions: returns: schema: type: string - - name: send_email_shuffle - description: Send an email from Shuffle - parameters: - - name: apikey - description: Your https://shuffler.io organization apikey - multiline: false - example: "https://shuffler.io apikey" - required: true - schema: - type: string - - name: recipients - description: The recipients of the email - multiline: false - example: "test@example.com,frikky@shuffler.io" - required: true - schema: - type: string - - name: subject - description: The subject to use - multiline: false - example: "SOS this is an alert :o" - required: true - schema: - type: string - - name: body - description: The body to add to the email - multiline: true - example: "This is an email alert from Shuffler.io :)" - required: true - schema: - type: string - - name: attachments - description: The ID of files in Shuffle to add as attachments - multiline: false - example: "file_id1,file_id2,file_id3" - required: false - schema: - type: string - returns: - schema: - type: string + #- name: send_email_shuffle + # description: Send an email from Shuffle + # parameters: + # - name: apikey + # description: Your https://shuffler.io organization apikey + # multiline: false + # example: "https://shuffler.io apikey" + # required: true + # schema: + # type: string + # - name: recipients + # description: The recipients of the email + # multiline: false + # example: "test@example.com,frikky@shuffler.io" + # required: true + # schema: + # type: string + # - name: subject + # description: The subject to use + # multiline: false + # example: "SOS this is an alert :o" + # required: true + # schema: + # type: string + # - name: body + # description: The body to add to the email + # multiline: true + # example: "This is an email alert from Shuffler.io :)" + # required: true + # schema: + # type: string + # - name: attachments + # description: The ID of files in Shuffle to add as attachments + # multiline: false + # example: "file_id1,file_id2,file_id3" + # required: false + # schema: + # type: string + # returns: + # schema: + # type: string - name: filter_list description: Takes a list and filters based on your data skip_multicheck: true diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index ae1e0f5c..77b556c8 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -198,6 +198,7 @@ def send_email_shuffle(self, apikey, recipients, subject, body, attachments=""): "subject": subject, "body": body, "type": "alert", + "email_app": True, } # Read the attachments @@ -214,9 +215,9 @@ def send_email_shuffle(self, apikey, recipients, subject, body, attachments=""): pass - url = "https://shuffler.io/api/v1/functions/sendmail" + url = "https://shuffler.io/functions/sendmail" headers = {"Authorization": "Bearer %s" % apikey} - return requests.post(url, headers=headers, json=data, verify=False).text + return requests.post(url, headers=headers, json=data).text def repeat_back_to_me(self, call): return call From 2cae4b49eb9f91feb92584a327ce159ee5004dcb Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 26 Jun 2024 13:15:35 +0200 Subject: [PATCH 128/259] Fixed item problem with parse-ioc --- shuffle-subflow/1.1.0/src/app.py | 52 ++++++++++++++++++++------------ shuffle-tools/1.2.0/src/app.py | 2 ++ 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/shuffle-subflow/1.1.0/src/app.py b/shuffle-subflow/1.1.0/src/app.py index 4eead3fb..ad741e68 100644 --- a/shuffle-subflow/1.1.0/src/app.py +++ b/shuffle-subflow/1.1.0/src/app.py @@ -29,6 +29,7 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" "User-Agent": "Shuffle Userinput 1.1.0", } + result = { "success": True, "source": "userinput", @@ -40,6 +41,11 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" "ip": "", "user": "", "note": "", + }, + "links": { + "frontend_no_answer": "", + "api_continue": "", + "api_abort": "", } } @@ -50,26 +56,32 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" if len(str(backend_url)) > 0: url = backend_url - print("Found backend url: %s" % url) - #print("AUTH: %s" % self.full_execution["authorization"]) - #if len(information): - # print("Should run arg: %s", information) + frontend_url = url + if ":5001" in frontend_url: + print("Should change port to 3001.") + if "appspot.com" in frontend_url: + frontend_url = "https://shuffler.io" + if "run.app" in frontend_url: + frontend_url = "https://shuffler.io" + if "ngrok" in frontend_url: + frontend_url = "" + + explore_path = "%s/workflows/%s/run?authorization=%s&reference_execution=%s&source_node=%s" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"], source_node) + frontend_continue_url = "%s/workflows/%s/run?authorization=%s&reference_execution=%s&answer=true&source_node=%s" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"], source_node) + frontend_abort_url = "%s/workflows/%s/run?authorization=%s&reference_execution=%s&answer=false&source_node=%s" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"], source_node) + api_continue_url = "%s/api/v1/workflows/%s/execute?authorization=%s&reference_execution=%s&answer=true&source_node=%s" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"], source_node) + api_abort_url = "%s/api/v1/workflows/%s/execute?authorization=%s&reference_execution=%s&answer=false&source_node=%s" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"], source_node) + + result["links"]["frontend_no_answer"] = explore_path + result["links"]["frontend_continue"] = frontend_continue_url + result["links"]["frontend_abort"] = frontend_abort_url + result["links"]["api_continue"] = api_continue_url + result["links"]["api_abort"] = api_abort_url + print("Found backend url: %s" % url) if len(subflow) > 0: - #print("Should run subflow: %s", subflow) - - # Missing startnode (user input trigger) - #print("Subflows to run from userinput: ", subflows) subflows = subflow.split(",") - frontend_url = url - if ":5001" in frontend_url: - print("Should change port to 3001.") - if "appspot.com" in frontend_url: - frontend_url = "https://shuffler.io" - if "run.app" in frontend_url: - frontend_url = "https://shuffler.io" - for item in subflows: # In case of URL being passed, and not just ID if "/" in item: @@ -80,10 +92,10 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" argument = json.dumps({ "information": information, "parent_workflow": self.full_execution["workflow"]["id"], - "frontend_continue": "%s/workflows/%s/run?authorization=%s&reference_execution=%s&answer=true&source_node=%s" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"], source_node), - "frontend_abort": "%s/workflows/%s/run?authorization=%s&reference_execution=%s&answer=false&source_node=%s" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"], source_node), - "api_continue": "%s/api/v1/workflows/%s/execute?authorization=%s&reference_execution=%s&answer=true&source_node=%s" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"], source_node), - "api_abort": "%s/api/v1/workflows/%s/execute?authorization=%s&reference_execution=%s&answer=false&source_node=%s" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"], source_node), + "frontend_continue": frontend_continue_url, + "frontend_abort": frontend_abort_url, + "api_continue": api_continue_url, + "api_abort": api_abort_url, }) ret = self.run_subflow(user_apikey, item, argument, source_workflow=self.full_execution["workflow"]["id"], source_execution=self.full_execution["execution_id"], source_auth=self.full_execution["authorization"], startnode=startnode, backend_url=backend_url, source_node=source_node) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 77b556c8..cc713f91 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -2480,6 +2480,8 @@ def parse_ioc(self, input_string, input_type="all"): else: input_type = input_type.split(",") for i in range(len(input_type)): + item = input_type[i] + item = item.strip() if not item.endswith("s"): item = "%ss" % item From da98c2e36a275dd8d126b3bf6e11a045d4d89eb0 Mon Sep 17 00:00:00 2001 From: Frikky Date: Tue, 23 Jul 2024 13:39:29 +0200 Subject: [PATCH 129/259] Moved crowdstrike to unsupported as we got a better openapi app --- crowdstrike-falcon/1.0.0/Dockerfile | 26 - crowdstrike-falcon/1.0.0/api.yaml | 17996 -------------------- crowdstrike-falcon/1.0.0/requirements.txt | 1 - crowdstrike-falcon/1.0.0/src/app.py | 3749 ---- 4 files changed, 21772 deletions(-) delete mode 100644 crowdstrike-falcon/1.0.0/Dockerfile delete mode 100755 crowdstrike-falcon/1.0.0/api.yaml delete mode 100644 crowdstrike-falcon/1.0.0/requirements.txt delete mode 100755 crowdstrike-falcon/1.0.0/src/app.py diff --git a/crowdstrike-falcon/1.0.0/Dockerfile b/crowdstrike-falcon/1.0.0/Dockerfile deleted file mode 100644 index 740fee62..00000000 --- a/crowdstrike-falcon/1.0.0/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -# Base our app image off of the WALKOFF App SDK image -FROM frikky/shuffle:app_sdk as base - -# We're going to stage away all of the bloat from the build tools so lets create a builder stage -FROM base as builder - -# Install all alpine build tools needed for our pip installs -RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev - -# Install all of our pip packages in a single directory that we can copy to our base image later -RUN mkdir /install -WORKDIR /install -COPY requirements.txt /requirements.txt -RUN pip install --prefix="/install" -r /requirements.txt - -# Switch back to our base image and copy in all of our built packages and source code -FROM base -COPY --from=builder /install /usr/local -COPY src /app - -# Install any binary dependencies needed in our final image - this can be a lot of different stuff -RUN apk --no-cache add --update libmagic - -# Finally, lets run our app! -WORKDIR /app -CMD python app.py --log-level DEBUG diff --git a/crowdstrike-falcon/1.0.0/api.yaml b/crowdstrike-falcon/1.0.0/api.yaml deleted file mode 100755 index c6738a17..00000000 --- a/crowdstrike-falcon/1.0.0/api.yaml +++ /dev/null @@ -1,17996 +0,0 @@ -name: Crowdstrike Falcon -is_valid: true -id: "" -link: https://api.crowdstrike.com -app_version: 1.0.0 -sharing_config: "" -generated: true -downloaded: false -sharing: false -verified: false -invalid: false -activated: true -tested: false -hash: "" -private_id: "" -description: Each API endpoint requires authorization via an OAuth2 token. Your first API request - should retrieve an OAuth2 token using the `oauth2/token` endpoint, such as `https://api.crowdstrike.com/oauth2/token`. Any action should be preceeded by a `get oauth2 access token` action titled `auth` that feeds the access token into it. Tokens expire after 30 minutes, after which you should make a new token request - to continue making API requests. -environment: Shuffle -contact_info: - name: "test" - url: "test" -referenceinfo: - documentationurl: "" - githuburl: "" -foldermount: - foldermount: false - sourcefolder: "" - destinationfolder: "" -actions: -- description: "" - name: generate_oauth2_access_token - label: OAuth2 - Generate an OAuth2 access token - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Accept: application/json - Content-Type: application/x-www-form-urlencoded - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_detect_aggregates - label: Detects - Get detect aggregates - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: view_information_about_detections - label: Detects - View information about detections - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Generated by shuffler.io OpenAPI - name: body - example: |- - { - "ids": "${ids}" - } - value: |- - { - "ids": "${ids}" - } - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: modify_detections - label: Detects - Modify the state assignee and visibility of detections - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Generated by shuffler.io OpenAPI - name: body - example: |- - { - "assigned_to_uuid": "${assigned_to_uuid}", - "comment": "${comment}", - "ids": "${ids}", - "show_in_ui": "${show_in_ui}", - "status": "${status}" - } - value: |- - { - "assigned_to_uuid": "${assigned_to_uuid}", - "comment": "${comment}", - "ids": "${ids}", - "show_in_ui": "${show_in_ui}", - "status": "${status}" - } - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: search_for_detection_ids - label: Detects - Search for detection IDs that match a given query - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The first detection to return, where `0` is the latest detection. - Use with the `limit` parameter to manage pagination of results. - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: 'The maximum number of detections to return in this response (default: - 9999; max: 9999). Use with the `offset` parameter to manage pagination of results.' - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: |- - Sort detections using these options: - - - `first_behavior`: Timestamp of the first behavior associated with this detection - - `last_behavior`: Timestamp of the last behavior associated with this detection - - `max_severity`: Highest severity of the behaviors associated with this detection - - `max_confidence`: Highest confidence of the behaviors associated with this detection - - `adversary_id`: ID of the adversary associated with this detection, if any - - `devices.hostname`: Hostname of the host where this detection was detected - - Sort either `asc` (ascending) or `desc` (descending). For example: `last_behavior|asc` - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: "Filter detections using a query in Falcon Query Language (FQL) An - asterisk wildcard `*` includes all results. \n\nCommon filter options include:\n\n- - `status`\n- `device.device_id`\n- `max_severity`\n\nThe full list of valid filter - options is extensive. Review it in our [documentation inside the Falcon console](https://falcon.crowdstrike.com/support/documentation/2/query-api-reference#detections_fql)." - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Search all detection metadata for the provided string - name: q - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: retrieve_set_of_host_groups - label: Host Group - Retrieve a set of Host Groups by specifying their IDs - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The IDs of the Host Groups to return - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: delete_set_of_host_groups - label: Host Group - Delete a set of Host Groups by specifying their IDs - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The IDs of the Host Groups to delete - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: create_host_groups - label: Host Group - Create Host Groups by specifying details about the group to create - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: update_host_groups - label: Host Group - Update Host Groups by specifying the ID of the group and details to update - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: search_for_host_groups - label: Host Group - Search for Host Groups in your environment by providing an FQL filter and - paging details Returns a set of Host Groups which match the filter criteria - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The filter expression that should be used to limit the results - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The offset to start retrieving records from - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The maximum records to return. [1-5000] - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The property to sort by - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: search_for_host_group_ids - label: Host Group - Search for Host Groups in your environment by providing an FQL filter and - paging details Returns a set of Host Group IDs which match the filter criteria - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The filter expression that should be used to limit the results - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The offset to start retrieving records from - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The maximum records to return. [1-5000] - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The property to sort by - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: search_for_host_group_members - label: Host Group - Search for members of a Host Group in your environment by providing an FQL - filter and paging details Returns a set of host details which match the filter - criteria - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The ID of the Host Group to search for members of - name: id - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The filter expression that should be used to limit the results - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The offset to start retrieving records from - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The maximum records to return. [1-5000] - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The property to sort by - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: perform_action_on_host_group - label: Host Group - Perform the specified action on the Host Groups specified in the request - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The action to perform - name: action_name - example: "" - multiline: false - options: - - add-hosts - - remove-hosts - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The ID of the host group to change - name: host_group_id - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The hostnames to change - name: hostnames - example: "" - multiline: true - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: search_for_host_group_member_ids - label: Host Group - Search for members of a Host Group in your environment by providing an FQL - filter and paging details Returns a set of Agent IDs which match the filter criteria - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The ID of the Host Group to search for members of - name: id - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The filter expression that should be used to limit the results - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The offset to start retrieving records from - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The maximum records to return. [1-5000] - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The property to sort by - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: retrieve_hidden_hosts - label: Hosts - Retrieve hidden hosts that match the provided filter criteria - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The offset to start retrieving records from - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The maximum records to return. [1-5000] - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The property to sort by (e.g. status.desc or hostname.asc) - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The filter expression that should be used to limit the results - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: search_for_hosts - label: Hosts - Search for hosts in your environment by platform hostname IP and other criteria - with continuous pagination capability based on offset pointer which expires after - 2 minutes with no maximum limit - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The offset to page from, for the next result set - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The maximum records to return. [1-5000] - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The property to sort by (e.g. status.desc or hostname.asc) - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The filter expression that should be used to limit the results - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: modify_host_tags - label: Hosts - Append or remove one or more Falcon Grouping Tags on one or more hosts - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Generated by shuffler.io OpenAPI - name: body - example: |- - { - "action": "${action}", - "device_ids": "${device_ids}", - "tags": "${tags}" - } - value: |- - { - "action": "${action}", - "device_ids": "${device_ids}", - "tags": "${tags}" - } - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_details_on_hosts - label: Hosts - Get details on one or more hosts by providing agent IDs AID You can get a - hosts agent IDs AIDs from the devicesqueriesdevicesv1 endpoint the Falcon console - or the Streaming API - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The host agentIDs used to get details on - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: take_action_on_hosts - label: Hosts - Take various actions on the hosts in your environment Contain or lift containment - on a host Delete or restore a host - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: |- - Specify one of these actions: - - - `contain` - This action contains the host, which stops any network communications to locations other than the CrowdStrike cloud and IPs specified in your [containment policy](https://falcon.crowdstrike.com/support/documentation/11/getting-started-guide#containmentpolicy) - - `lift_containment`: This action lifts containment on the host, which returns its network communications to normal - - `hide_host`: This action will delete a host. After the host is deleted, no new detections for that host will be reported via UI or APIs - - `unhide_host`: This action will restore a host. Detection reporting will resume after the host is restored - name: action_name - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Generated by shuffler.io OpenAPI - name: body - example: |- - { - "action_parameters": "${action_parameters}", - "ids": "${ids}" - } - value: |- - { - "action_parameters": "${action_parameters}", - "ids": "${ids}" - } - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: search_for_hosts - label: Hosts - Search for hosts in your environment by platform hostname IP and other criteria - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The offset to start retrieving records from - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The maximum records to return. [1-5000] - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The property to sort by (e.g. status.desc or hostname.asc) - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The filter expression that should be used to limit the results - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: download_analysis_artifacts - label: FalconX Sandbox - Download IOC packs PCAP files and other analysis artifacts - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: ID of an artifact, such as an IOC pack, PCAP file, or actor image. - Find an artifact ID in a report or summary. - name: id - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: gzip - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The name given to your downloaded file. - name: name - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_sandbox_reports - label: FalconX Sandbox - Find sandbox reports by providing an FQL filter and paging details Returns - a set of report IDs that match your criteria - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Optional filter and sort criteria in the form of an FQL query. For - more information about FQL queries, see [our FQL documentation in Falcon](https://falcon.crowdstrike.com/support/documentation/45/falcon-query-language-feature-guide). - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The offset to start retrieving reports from. - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: 'Maximum number of report IDs to return. Max: 5000.' - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: 'Sort order: `asc` or `desc`.' - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_a_full_sandbox_report - label: FalconX Sandbox - Get a full sandbox report - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: ID of a report. Find a report ID from the response when submitting - a malware sample or search with `/falconx/queries/reports/v1`. - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: delete_report - label: FalconX Sandbox - Delete report based on the report ID Operation can be checked for success - by polling for the report ID on the reportsummaries endpoint - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: ID of a report. - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: retrieve_list_of_samples - label: FalconX Sandbox - retrieve a list with sha256 of samples that exist and customer has rights - to access them maximum number of accepted items is 200 - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Generated by shuffler.io OpenAPI - name: body - example: |- - { - "sha256s": "${sha256s}" - } - value: |- - { - "sha256s": "${sha256s}" - } - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: check_status_of_sandbox_analysis - label: FalconX Sandbox - Check the status of a sandbox analysis Time required for analysis varies - but is usually less than 15 minutes - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: ID of a submitted malware sample. Find a submission ID from the response - when submitting a malware sample or search with `/falconx/queries/submissions/v1`. - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: submit_upload_for_sandbox_analysis - label: FalconX Sandbox - Submit an uploaded file or a URL for sandbox analysis Time required for analysis - varies but is usually less than 15 minutes - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_a_short_summary_version_of_a_sandbox_report - label: FalconX Sandbox - Get a short summary version of a sandbox report - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: ID of a summary. Find a summary ID from the response when submitting - a malware sample or search with `/falconx/queries/reports/v1`. - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: find_submission_ids_for_uploaded_files - label: FalconX Sandbox - Find submission IDs for uploaded files by providing an FQL filter and paging - details Returns a set of submission IDs that match your criteria - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Optional filter and sort criteria in the form of an FQL query. For - more information about FQL queries, see [our FQL documentation in Falcon](https://falcon.crowdstrike.com/support/documentation/45/falcon-query-language-feature-guide). - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The offset to start retrieving submissions from. - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: 'Maximum number of submission IDs to return. Max: 5000.' - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: 'Sort order: `asc` or `desc`.' - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: retrieve_the_file_associated_with_the_given_id_sha256 - label: FalconX Sandbox - retrieve the file associated with the given ID SHA256 - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The file SHA256. - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Flag whether the sample should be zipped and password protected with - pass='infected' - name: password_protected - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: delete_sample_from_the_collection - label: FalconX Sandbox - Removes a sample including file meta and submissions from the collection - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The file SHA256. - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: upload_for_sandbox_analysis - label: FalconX Sandbox - Upload a file for sandbox analysis After uploading use falconxentitiessubmissionsv1 - to start analyzing the file - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Name of the file. - name: file_name - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: A descriptive comment to identify the file for other users. - name: comment - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: "Defines visibility of this file in Falcon MalQuery, either via the - API or the Falcon console.\n\n- `true`: File is only shown to users within your - customer account\n- `false`: File can be seen by other CrowdStrike customers - \n\nDefault: `true`." - name: is_confidential - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: search_for_behaviors - label: Incidents - Search for behaviors by providing an FQL filter sorting and paging details - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Optional filter and sort criteria in the form of an FQL query. For - more information about FQL queries, see [our FQL documentation in Falcon](https://falcon.crowdstrike.com/support/documentation/45/falcon-query-language-feature-guide). - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Starting index of overall result set from which to return ids. - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The maximum records to return. [1-500] - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The property to sort on, followed by a dot (.), followed by the sort - direction, either "asc" or "desc". - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: search_for_incidents - label: Incidents - Search for incidents by providing an FQL filter sorting and paging details - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The property to sort on, followed by a dot (.), followed by the sort - direction, either "asc" or "desc". - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Optional filter and sort criteria in the form of an FQL query. For - more information about FQL queries, see [our FQL documentation in Falcon](https://falcon.crowdstrike.com/support/documentation/45/falcon-query-language-feature-guide). - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Starting index of overall result set from which to return ids. - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The maximum records to return. [1-500] - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: query_crowdscore - label: Incidents - Query environment wide CrowdScore and return the entity data - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Optional filter and sort criteria in the form of an FQL query. For - more information about FQL queries, see [our FQL documentation in Falcon](https://falcon.crowdstrike.com/support/documentation/45/falcon-query-language-feature-guide). - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Starting index of overall result set from which to return ids. - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The maximum records to return. [1-2500] - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The property to sort on, followed by a dot (.), followed by the sort - direction, either "asc" or "desc". - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: perform_actions_on_incidents - label: Incidents - Perform a set of actions on one or more incidents such as adding tags or - comments or updating the incident name or description - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Generated by shuffler.io OpenAPI - name: body - example: |- - { - "action_parameters": "${action_parameters}", - "ids": "${ids}" - } - value: |- - { - "action_parameters": "${action_parameters}", - "ids": "${ids}" - } - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_details_on_behaviors - label: Incidents - Get details on behaviors by providing behavior IDs - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Generated by shuffler.io OpenAPI - name: body - example: |- - { - "ids": "${ids}" - } - value: |- - { - "ids": "${ids}" - } - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_details_on_incidents - label: Incidents - Get details on incidents by providing incident IDs - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Generated by shuffler.io OpenAPI - name: body - example: |- - { - "ids": "${ids}" - } - value: |- - { - "ids": "${ids}" - } - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_combined_for_indicators - label: IOCs - Get Combined for Indicators - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The filter expression that should be used to limit the results. - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The offset to start retrieving records from. Offset and After params - are mutually exclusive. If none provided then scrolling will be used by default. - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The maximum records to return. - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The sort expression that should be used to sort the results. - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_indicators_by_ids - label: IOCs - Get Indicators by ids - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The ids of the Indicators to retrieve - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: delete_indicators_by_ids - label: IOCs - Delete Indicators by ids - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The FQL expression to delete Indicators in bulk. If both 'filter' - and 'ids' are provided, then filter takes precedence and ignores ids. - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The ids of the Indicators to delete. If both 'filter' and 'ids' are - provided, then filter takes precedence and ignores ids - name: ids - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The comment why these indicators were deleted - name: comment - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: create_indicators - label: IOCs - Create Indicators - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Whether to submit to retrodetects - name: retrodetects - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Set to true to ignore warnings and add all IOCs - name: ignore_warnings - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Generated by shuffler.io OpenAPI - name: body - example: |- - { - "comment": "${comment}", - "indicators": "${indicators}" - } - value: |- - { - "comment": "${comment}", - "indicators": "${indicators}" - } - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: update_indicators - label: IOCs - Update Indicators - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Whether to submit to retrodetects - name: retrodetects - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Set to true to ignore warnings and add all IOCs - name: ignore_warnings - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Generated by shuffler.io OpenAPI - name: body - example: |- - { - "bulk_update": "${bulk_update}", - "comment": "${comment}", - "indicators": "${indicators}" - } - value: |- - { - "bulk_update": "${bulk_update}", - "comment": "${comment}", - "indicators": "${indicators}" - } - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_number_of_hosts_that_have_observed_a_given_custom_ioc - label: IOCs - Number of hosts in your customer account that have observed a given custom - IOC - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: |2 - - The type of the indicator. Valid types include: - - sha256: A hex-encoded sha256 hash string. Length - min: 64, max: 64. - - md5: A hex-encoded md5 hash string. Length - min 32, max: 32. - - domain: A domain name. Length - min: 1, max: 200. - - ipv4: An IPv4 address. Must be a valid IP address. - - ipv6: An IPv6 address. Must be a valid IP address. - name: type - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The string representation of the indicator - name: value - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_process_details - label: IOCs - For the provided ProcessID retrieve the process details - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: ProcessID for the running process you want to lookup - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_hosts_that_have_observed_a_given_custom_ioc - label: IOCs - Find hosts that have observed a given custom IOC For details about those - hosts use GET devicesentitiesdevicesv1 - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: |2 - - The type of the indicator. Valid types include: - - sha256: A hex-encoded sha256 hash string. Length - min: 64, max: 64. - - md5: A hex-encoded md5 hash string. Length - min 32, max: 32. - - domain: A domain name. Length - min: 1, max: 200. - - ipv4: An IPv4 address. Must be a valid IP address. - - ipv6: An IPv6 address. Must be a valid IP address. - name: type - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The string representation of the indicator - name: value - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The first process to return, where 0 is the latest offset. Use with - the offset parameter to manage pagination of results. - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The first process to return, where 0 is the latest offset. Use with - the limit parameter to manage pagination of results. - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: search_for_processes_associated_with_a_custom_ioc - label: IOCs - Search for processes associated with a custom IOC - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: |2 - - The type of the indicator. Valid types include: - - sha256: A hex-encoded sha256 hash string. Length - min: 64, max: 64. - - md5: A hex-encoded md5 hash string. Length - min 32, max: 32. - - domain: A domain name. Length - min: 1, max: 200. - - ipv4: An IPv4 address. Must be a valid IP address. - - ipv6: An IPv6 address. Must be a valid IP address. - name: type - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The string representation of the indicator - name: value - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Specify a host's ID to return only processes from that host. Get - a host's ID from GET /devices/queries/devices/v1, the Falcon console, or the - Streaming API. - name: device_id - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The first process to return, where 0 is the latest offset. Use with - the offset parameter to manage pagination of results. - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The first process to return, where 0 is the latest offset. Use with - the limit parameter to manage pagination of results. - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: search_for_indicators - label: IOCs - Search for Indicators - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The filter expression that should be used to limit the results. - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The offset to start retrieving records from. Offset and After params - are mutually exclusive. If none provided then scrolling will be used by default. - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The maximum records to return. - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The sort expression that should be used to sort the results. - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_info_about_indicators - label: Intel - Get info about indicators that match provided FQL filters - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Set the starting row number to return indicators from. Defaults to - 0. - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Set the number of indicators to return. The number must be between - 1 and 50000 - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: |- - Order fields in ascending or descending order. - - Ex: published_date|asc. - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: |- - Filter your query by specifying FQL filter parameters. Filter parameters include: - - _marker, actors, deleted, domain_types, id, indicator, ip_address_types, kill_chains, labels, labels.created_on, labels.last_valid_on, labels.name, last_updated, malicious_confidence, malware_families, published_date, reports, targets, threat_types, type, vulnerabilities. - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Perform a generic substring search across all fields. - name: q - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: If true, include both published and deleted indicators in the response. - Defaults to false. - name: include_deleted - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: download_earlier_rule_sets - label: Intel - Download earlier rule sets - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The ID of the rule set. - name: id - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Choose the format you want the rule set in. Valid formats are zip - and gzip. Defaults to zip. - name: format - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_report_ids - label: Intel - Get report IDs that match provided FQL filters - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Set the starting row number to return report IDs from. Defaults to - 0. - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Set the number of report IDs to return. The value must be between - 1 and 5000. - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: |- - Order fields in ascending or descending order. - - Ex: created_date|asc. - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: |- - Filter your query by specifying FQL filter parameters. Filter parameters include: - - actors, actors.id, actors.name, actors.slug, actors.url, created_date, description, id, last_modified_date, motivations, motivations.id, motivations.slug, motivations.value, name, name.raw, short_description, slug, sub_type, sub_type.id, sub_type.name, sub_type.slug, tags, tags.id, tags.slug, tags.value, target_countries, target_countries.id, target_countries.slug, target_countries.value, target_industries, target_industries.id, target_industries.slug, target_industries.value, type, type.id, type.name, type.slug, url. - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Perform a generic substring search across all fields. - name: q - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: search_for_rule_ids - label: Intel - Search for rule IDs that match provided filter criteria - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: |- - The rule news report type. Accepted values: - - snort-suricata-master - - snort-suricata-update - - snort-suricata-changelog - - yara-master - - yara-update - - yara-changelog - - common-event-format - - netwitness - name: type - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Set the starting row number to return reports from. Defaults to 0. - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The number of rule IDs to return. Defaults to 10. - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: |- - Order fields in ascending or descending order. - - Ex: created_date|asc. - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Search by rule title. - name: name - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Substring match on description field. - name: description - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Search for rule tags. - name: tags - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Filter results to those created on or after a certain date. - name: min_created_date - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Filter results to those created on or before a certain date. - name: max_created_date - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Perform a generic substring search across all fields. - name: q - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_info_about_reports - label: Intel - Get info about reports that match provided FQL filters - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Set the starting row number to return reports from. Defaults to 0. - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Set the number of reports to return. The value must be between 1 - and 5000. - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: 'Order fields in ascending or descending order. Ex: created_date|asc.' - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: |- - Filter your query by specifying FQL filter parameters. Filter parameters include: - - actors, actors.id, actors.name, actors.slug, actors.url, created_date, description, id, last_modified_date, motivations, motivations.id, motivations.slug, motivations.value, name, name.raw, short_description, slug, sub_type, sub_type.id, sub_type.name, sub_type.slug, tags, tags.id, tags.slug, tags.value, target_countries, target_countries.id, target_countries.slug, target_countries.value, target_industries, target_industries.id, target_industries.slug, target_industries.value, type, type.id, type.name, type.slug, url. - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Perform a generic substring search across all fields. - name: q - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: |- - The fields to return, or a predefined set of fields in the form of the collection name surrounded by two underscores like: - - \_\_\\_\_. - - Ex: slug \_\_full\_\_. - - Defaults to \_\_basic\_\_. - name: fields - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_indicators_ids - label: Intel - Get indicators IDs that match provided FQL filters - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Set the starting row number to return indicator IDs from. Defaults - to 0. - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Set the number of indicator IDs to return. The number must be between - 1 and 50000 - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: |- - Order fields in ascending or descending order. - - Ex: published_date|asc. - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: |- - Filter your query by specifying FQL filter parameters. Filter parameters include: - - _marker, actors, deleted, domain_types, id, indicator, ip_address_types, kill_chains, labels, labels.created_on, labels.last_valid_on, labels.name, last_updated, malicious_confidence, malware_families, published_date, reports, targets, threat_types, type, vulnerabilities. - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Perform a generic substring search across all fields. - name: q - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: If true, include both published and deleted indicators in the response. - Defaults to false. - name: include_deleted - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: retrieve_specific_actors_using_their_actor_ids - label: Intel - Retrieve specific actors using their actor IDs - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Generated by shuffler.io OpenAPI - name: body - example: |- - { - "ids": "${ids}" - } - value: |- - { - "ids": "${ids}" - } - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: retrieve_specific_indicators_using_their_indicator_ids - label: Intel - Retrieve specific indicators using their indicator IDs - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Generated by shuffler.io OpenAPI - name: body - example: |- - { - "ids": "${ids}" - } - value: |- - { - "ids": "${ids}" - } - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_info_about_actors - label: Intel - Get info about actors that match provided FQL filters - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Set the starting row number to return actors from. Defaults to 0. - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Set the number of actors to return. The value must be between 1 and - 5000. - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: |- - Order fields in ascending or descending order. - - Ex: created_date|asc. - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: |- - Filter your query by specifying FQL filter parameters. Filter parameters include: - - actors, actors.id, actors.name, actors.slug, actors.url, created_date, description, id, last_modified_date, motivations, motivations.id, motivations.slug, motivations.value, name, name.raw, short_description, slug, sub_type, sub_type.id, sub_type.name, sub_type.slug, tags, tags.id, tags.slug, tags.value, target_countries, target_countries.id, target_countries.slug, target_countries.value, target_industries, target_industries.id, target_industries.slug, target_industries.value, type, type.id, type.name, type.slug, url. - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Perform a generic substring search across all fields. - name: q - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: |- - The fields to return, or a predefined set of fields in the form of the collection name surrounded by two underscores like: - - \_\_\\_\_. - - Ex: slug \_\_full\_\_. - - Defaults to \_\_basic\_\_. - name: fields - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_a_report_pdf_attachment - label: Intel - Return a Report PDF attachment - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The ID of the report you want to download as a PDF. - name: id - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: download_the_latest_rule_set - label: Intel - Download the latest rule set - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: |- - The rule news report type. Accepted values: - - snort-suricata-master - - snort-suricata-update - - snort-suricata-changelog - - yara-master - - yara-update - - yara-changelog - - common-event-format - - netwitness - name: type - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Choose the format you want the rule set in. Valid formats are zip - and gzip. Defaults to zip. - name: format - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: retrieve_details_for_rule_sets_for_ids - label: Intel - Retrieve details for rule sets for the specified ids - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The ids of rules to return. - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_actor_ids - label: Intel - Get actor IDs that match provided FQL filters - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Set the starting row number to return actors IDs from. Defaults to - 0. - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Set the number of actor IDs to return. The value must be between - 1 and 5000. - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: |- - Order fields in ascending or descending order. - - Ex: created_date|asc. - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: |- - Filter your query by specifying FQL filter parameters. Filter parameters include: - - actors, actors.id, actors.name, actors.slug, actors.url, created_date, description, id, last_modified_date, motivations, motivations.id, motivations.slug, motivations.value, name, name.raw, short_description, slug, sub_type, sub_type.id, sub_type.name, sub_type.slug, tags, tags.id, tags.slug, tags.value, target_countries, target_countries.id, target_countries.slug, target_countries.value, target_industries, target_industries.id, target_industries.slug, target_industries.value, type, type.id, type.name, type.slug, url. - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Perform a generic substring search across all fields. - name: q - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: retrieve_specific_reports_using_their_report_ids - label: Intel - Retrieve specific reports using their report IDs - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The IDs of the reports you want to retrieve. - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: |- - The fields to return, or a predefined set of fields in the form of the collection name surrounded by two underscores like: - - \_\_\\_\_. - - Ex: slug \_\_full\_\_. - - Defaults to \_\_basic\_\_. - name: fields - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_rules_by_id - label: Custom IOA - Get rules by ID - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The IDs of the entities - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: delete_rules_from_a_rule_group_by_id - label: Custom IOA - Delete rules from a rule group by ID - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The parent rule group - name: rule_group_id - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The IDs of the entities - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Explains why the entity is being deleted - name: comment - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: create_a_rule_within_a_rule_group - label: Custom IOA - Create a rule within a rule group - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Generated by shuffler.io OpenAPI - name: body - example: |- - { - "comment": "${comment}", - "description": "${description}", - "disposition_id": "${disposition_id}", - "field_values": "${field_values}", - "name": "${name}", - "pattern_severity": "${pattern_severity}", - "rulegroup_id": "${rulegroup_id}", - "ruletype_id": "${ruletype_id}" - } - value: |- - { - "comment": "${comment}", - "description": "${description}", - "disposition_id": "${disposition_id}", - "field_values": "${field_values}", - "name": "${name}", - "pattern_severity": "${pattern_severity}", - "rulegroup_id": "${rulegroup_id}", - "ruletype_id": "${ruletype_id}" - } - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: update_rules_within_a_rule_group - label: Custom IOA - Update rules within a rule group - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Generated by shuffler.io OpenAPI - name: body - example: |- - { - "comment": "${comment}", - "rule_updates": "${rule_updates}", - "rulegroup_id": "${rulegroup_id}", - "rulegroup_version": "${rulegroup_version}" - } - value: |- - { - "comment": "${comment}", - "rule_updates": "${rule_updates}", - "rulegroup_id": "${rulegroup_id}", - "rulegroup_version": "${rulegroup_version}" - } - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_rule_types_by_id - label: Custom IOA - Get rule types by ID - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The IDs of the entities - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_all_platform_ids - label: Custom IOA - Get all platform IDs - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Starting index of overall result set from which to return IDs - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Number of IDs to return - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: find_all_rule_ids - label: Custom IOA - Finds all rule IDs matching the query with optional filter - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: 'Possible order by fields: {rules.ruletype_name, rules.enabled, rules.created_by, - rules.current_version.name, rules.current_version.modified_by, rules.created_on, - rules.current_version.description, rules.current_version.pattern_severity, rules.current_version.action_label, - rules.current_version.modified_on}' - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: 'FQL query specifying the filter parameters. Filter term criteria: - [enabled platform name description rules.action_label rules.name rules.description - rules.pattern_severity rules.ruletype_name rules.enabled]. Filter range criteria: - created_on, modified_on; use any common date format, such as ''2010-05-15T14:55:21.892315096Z''.' - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Match query criteria, which includes all the filter string fields - name: q - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Starting index of overall result set from which to return IDs - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Number of IDs to return - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: find_all_rule_group_ids - label: Custom IOA - Finds all rule group IDs matching the query with optional filter - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: 'Possible order by fields: {created_by, created_on, modified_by, - modified_on, enabled, name, description}' - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: 'FQL query specifying the filter parameters. Filter term criteria: - [enabled platform name description rules.action_label rules.name rules.description - rules.pattern_severity rules.ruletype_name rules.enabled]. Filter range criteria: - created_on, modified_on; use any common date format, such as ''2010-05-15T14:55:21.892315096Z''.' - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Match query criteria, which includes all the filter string fields - name: q - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Starting index of overall result set from which to return IDs - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Number of IDs to return - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_rule_groups_by_id - label: Custom IOA - Get rule groups by ID - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The IDs of the entities - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: delete_rule_groups_by_id - label: Custom IOA - Delete rule groups by ID - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The IDs of the entities - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Explains why the entity is being deleted - name: comment - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: create_a_rule_group - label: Custom IOA - Create a rule group for a platform with a name and an optional description - Returns the rule group - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Generated by shuffler.io OpenAPI - name: body - example: |- - { - "comment": "${comment}", - "description": "${description}", - "name": "${name}", - "platform": "${platform}" - } - value: |- - { - "comment": "${comment}", - "description": "${description}", - "name": "${name}", - "platform": "${platform}" - } - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: update_a_rule_group - label: Custom IOA - Update a rule group The following properties can be modified name description - enabled - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Generated by shuffler.io OpenAPI - name: body - example: |- - { - "comment": "${comment}", - "description": "${description}", - "enabled": "${enabled}", - "id": "${id}", - "name": "${name}", - "rulegroup_version": "${rulegroup_version}" - } - value: |- - { - "comment": "${comment}", - "description": "${description}", - "enabled": "${enabled}", - "id": "${id}", - "name": "${name}", - "rulegroup_version": "${rulegroup_version}" - } - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_all_rule_type_ids - label: Custom IOA - Get all rule type IDs - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Starting index of overall result set from which to return IDs - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Number of IDs to return - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_all_pattern_severity_ids - label: Custom IOA - Get all pattern severity IDs - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Starting index of overall result set from which to return IDs - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Number of IDs to return - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: validates_field_values_and_checks_for_string_matches - label: Custom IOA - Validates field values and checks for matches if a test string is provided - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Generated by shuffler.io OpenAPI - name: body - example: |- - { - "fields": "${fields}" - } - value: |- - { - "fields": "${fields}" - } - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_rules_by_id - label: Custom IOA - Get rules by ID and optionally version in the following format IDversion - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Generated by shuffler.io OpenAPI - name: body - example: |- - { - "ids": "${ids}" - } - value: |- - { - "ids": "${ids}" - } - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: find_all_rule_groups - label: Custom IOA - Find all rule groups matching the query with optional filter - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: 'Possible order by fields: {created_by, created_on, modified_by, - modified_on, enabled, name, description}' - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: 'FQL query specifying the filter parameters. Filter term criteria: - [enabled platform name description rules.action_label rules.name rules.description - rules.pattern_severity rules.ruletype_name rules.enabled]. Filter range criteria: - created_on, modified_on; use any common date format, such as ''2010-05-15T14:55:21.892315096Z''.' - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Match query criteria, which includes all the filter string fields - name: q - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Starting index of overall result set from which to return IDs - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Number of IDs to return - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_platforms_by_id - label: Custom IOA - Get platforms by ID - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The IDs of the entities - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_pattern_severities_by_id - label: Custom IOA - Get pattern severities by ID - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The IDs of the entities - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_a_zipped_sample - label: Malquery - Fetch a zip archive with password infected containing the samples Call this - once the entitiessamplesmultidownload request has finished processing - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Multidownload job id - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: schedule_samples_for_download - label: Malquery - Schedule samples for download Use the result id with the request endpoint - to check if the download is ready after which you can call the entitiessamplesfetch - to get the zip - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Generated by shuffler.io OpenAPI - name: body - example: |- - { - "samples": "${samples}" - } - value: |- - { - "samples": "${samples}" - } - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: search_falcon_malquery - label: Malquery - Search Falcon MalQuery for a combination of hex patterns and strings in order - to identify samples based upon file content at byte level granularity You can - filter results on criteria such as file type file size and first seen date Returns - a request id which can be used with the request endpoint - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Generated by shuffler.io OpenAPI - name: body - example: |- - { - "options": "${options}", - "patterns": "${patterns}" - } - value: |- - { - "options": "${options}", - "patterns": "${patterns}" - } - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_information_about_search_and_download_quotas - label: Malquery - Get information about search and download quotas in your environment - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: retrieve_indexed_files_metadata_by_their_hash - label: Malquery - Retrieve indexed files metadata by their hash - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The file SHA256. - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: schedule_a_yara_based_search_for_execution - label: Malquery - Schedule a YARAbased search for execution Returns a request id which can - be used with the request endpoint - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Generated by shuffler.io OpenAPI - name: body - example: |- - { - "options": "${options}", - "yara_rule": "${yara_rule}" - } - value: |- - { - "options": "${options}", - "yara_rule": "${yara_rule}" - } - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: check_the_status_and_results_of_an_asynchronous_request - label: Malquery - Check the status and results of an asynchronous request such as hunt or exactsearch - Supports a single request id at this time - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Identifier of a MalQuery request - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: download_a_file_indexed_by_malquery - label: Malquery - Download a file indexed by MalQuery Specify the file using its SHA256 Only - one file is supported at this time - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The file SHA256. - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: revoke_oauth2_access_token - label: OAuth2 - Revoke a previously issued OAuth2 access token before the end of its standard - 30minute lifespan - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Accept-Encoding: application/json - Content-Type: application/x-www-form-urlencoded - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: search_for_device_control_policy_ids - label: Device Control Policies - Search for Device Control Policies in your environment by providing an FQL - filter and paging details Returns a set of Device Control Policy IDs which match - the filter criteria - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The filter expression that should be used to limit the results - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The offset to start retrieving records from - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The maximum records to return. [1-5000] - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The property to sort by - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: search_for_device_control_policy_members - label: Device Control Policies - Search for members of a Device Control Policy in your environment by providing - an FQL filter and paging details Returns a set of host details which match the - filter criteria - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The ID of the Device Control Policy to search for members of - name: id - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The filter expression that should be used to limit the results - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The offset to start retrieving records from - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The maximum records to return. [1-5000] - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The property to sort by - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: search_for_device_control_policies - label: Device Control Policies - Search for Device Control Policies in your environment by providing an FQL - filter and paging details Returns a set of Device Control Policies which match - the filter criteria - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The filter expression that should be used to limit the results - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The offset to start retrieving records from - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The maximum records to return. [1-5000] - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The property to sort by - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: search_for_device_control_policy_member_ids - label: Device Control Policies - Search for members of a Device Control Policy in your environment by providing - an FQL filter and paging details Returns a set of Agent IDs which match the filter - criteria - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The ID of the Device Control Policy to search for members of - name: id - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The filter expression that should be used to limit the results - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The offset to start retrieving records from - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The maximum records to return. [1-5000] - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The property to sort by - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: set_precedence_of_device_control_policies - label: Device Control Policies - Sets the precedence of Device Control Policies based on the order of IDs - specified in the request The first ID specified will have the highest precedence - and the last ID specified will have the lowest You must specify all nonDefault - Policies for a platform when updating precedence - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: perform_action_on_the_device_control_policies - label: Device Control Policies - Perform the specified action on the Device Control Policies specified in - the request - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The action to perform - name: action_name - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: retrieve_a_set_of_device_control_policies - label: Device Control Policies - Retrieve a set of Device Control Policies by specifying their IDs - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The IDs of the Device Control Policies to return - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: delete_a_set_of_device_control_policies - label: Device Control Policies - Delete a set of Device Control Policies by specifying their IDs - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The IDs of the Device Control Policies to delete - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: create_device_control_policies - label: Device Control Policies - Create Device Control Policies by specifying details about the policy to - create - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: update_device_control_policies - label: Device Control Policies - Update Device Control Policies by specifying the ID of the policy and details - to update - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: search_for_firewall_policies - label: Firewall Policies - Search for Firewall Policies in your environment by providing an FQL filter - and paging details Returns a set of Firewall Policy IDs which match the filter - criteria - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The filter expression that should be used to limit the results - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The offset to start retrieving records from - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The maximum records to return. [1-5000] - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The property to sort by - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: set_precedence_of_firewall_policies - label: Firewall Policies - Sets the precedence of Firewall Policies based on the order of IDs specified - in the request The first ID specified will have the highest precedence and the - last ID specified will have the lowest You must specify all nonDefault Policies - for a platform when updating precedence - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: perform_action_on_the_firewall_policies - label: Firewall Policies - Perform the specified action on the Firewall Policies specified in the request - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The action to perform - name: action_name - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: search_for_firewall_policy_member_ids - label: Firewall Policies - Search for members of a Firewall Policy in your environment by providing - an FQL filter and paging details Returns a set of Agent IDs which match the filter - criteria - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The ID of the Firewall Policy to search for members of - name: id - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The filter expression that should be used to limit the results - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The offset to start retrieving records from - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The maximum records to return. [1-5000] - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The property to sort by - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: search_for_firewall_policies - label: Firewall Policies - Search for Firewall Policies in your environment by providing an FQL filter - and paging details Returns a set of Firewall Policies which match the filter criteria - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The filter expression that should be used to limit the results - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The offset to start retrieving records from - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The maximum records to return. [1-5000] - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The property to sort by - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: retrieve_a_set_of_firewall_policies - label: Firewall Policies - Retrieve a set of Firewall Policies by specifying their IDs - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The IDs of the Firewall Policies to return - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: delete_a_set_of_firewall_policies - label: Firewall Policies - Delete a set of Firewall Policies by specifying their IDs - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The IDs of the Firewall Policies to delete - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: create_firewall_policies - label: Firewall Policies - Create Firewall Policies by specifying details about the policy to create - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The policy ID to be cloned from - name: clone_id - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: update_firewall_policies - label: Firewall Policies - Update Firewall Policies by specifying the ID of the policy and details to - update - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: search_for_firewall_policy_members - label: Firewall Policies - Search for members of a Firewall Policy in your environment by providing - an FQL filter and paging details Returns a set of host details which match the - filter criteria - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The ID of the Firewall Policy to search for members of - name: id - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The filter expression that should be used to limit the results - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The offset to start retrieving records from - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The maximum records to return. [1-5000] - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The property to sort by - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: search_for_prevention_policy_members - label: Prevention Policies - Search for members of a Prevention Policy - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The ID of the Prevention Policy to search for members of - name: id - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The filter expression that should be used to limit the results - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The offset to start retrieving records from - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The maximum records to return. [1-5000] - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The property to sort by - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: search_for_prevention_policy_ids - label: Prevention Policies - Search for Prevention Policies in your environment by providing an FQL filter - and paging details Returns a set of Prevention Policy IDs which match the filter - criteria - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The filter expression that should be used to limit the results - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The offset to start retrieving records from - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The maximum records to return. [1-5000] - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The property to sort by - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: search_for_prevention_policies - label: Prevention Policies - Search for Prevention Policies in your environment by providing an FQL filter - and paging details Returns a set of Prevention Policies which match the filter - criteria - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The filter expression that should be used to limit the results - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The offset to start retrieving records from - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The maximum records to return. [1-5000] - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The property to sort by - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: set_precedence_of_prevention_policies - label: Prevention Policies - Sets the precedence of Prevention Policies based on the order of IDs specified - in the request The first ID specified will have the highest precedence and the - last ID specified will have the lowest You must specify all nonDefault Policies - for a platform when updating precedence - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: retrieve_a_set_of_prevention_policies - label: Prevention Policies - Retrieve a set of Prevention Policies by specifying their IDs - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The IDs of the Prevention Policies to return - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: delete_a_set_of_prevention_policies - label: Prevention Policies - Delete a set of Prevention Policies by specifying their IDs - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The IDs of the Prevention Policies to delete - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: create_prevention_policies - label: Prevention Policies - Create Prevention Policies by specifying details about the policy to create - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: update_prevention_policies - label: Prevention Policies - Update Prevention Policies by specifying the ID of the policy and details - to update - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: search_for_prevention_policy_member_ids - label: Prevention Policies - Search for members of a Prevention Policy in your environment by providing - an FQL filter and paging details Returns a set of Agent IDs which match the filter - criteria - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The ID of the Prevention Policy to search for members of - name: id - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The filter expression that should be used to limit the results - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The offset to start retrieving records from - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The maximum records to return. [1-5000] - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The property to sort by - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: perform_action_on_the_prevention_policies - label: Prevention Policies - Perform the specified action on the Prevention Policies specified in the - request - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The action to perform - name: action_name - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: set_precedence_of_response_policies - label: Response Policies - Sets the precedence of Response Policies based on the order of IDs specified - in the request The first ID specified will have the highest precedence and the - last ID specified will have the lowest You must specify all nonDefault Policies - for a platform when updating precedence - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: search_for_response_policy_members - label: Response Policies - Search for members of a Response policy in your environment by providing - an FQL filter and paging details Returns a set of host details which match the - filter criteria - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The ID of the Response policy to search for members of - name: id - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The filter expression that should be used to limit the results - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The offset to start retrieving records from - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The maximum records to return. [1-5000] - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The property to sort by - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: search_for_response_policy_member_ids - label: Response Policies - Search for members of a Response policy in your environment by providing - an FQL filter and paging details Returns a set of Agent IDs which match the filter - criteria - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The ID of the Response policy to search for members of - name: id - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The filter expression that should be used to limit the results - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The offset to start retrieving records from - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The maximum records to return. [1-5000] - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The property to sort by - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: perform_action_on_the_response_policies - label: Response Policies - Perform the specified action on the Response Policies specified in the request - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The action to perform - name: action_name - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: retrieve_a_set_of_response_policies - label: Response Policies - Retrieve a set of Response Policies by specifying their IDs - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The IDs of the RTR Policies to return - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: delete_a_set_of_response_policies - label: Response Policies - Delete a set of Response Policies by specifying their IDs - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The IDs of the Response Policies to delete - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: create_response_policies - label: Response Policies - Create Response Policies by specifying details about the policy to create - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: update_response_policies - label: Response Policies - Update Response Policies by specifying the ID of the policy and details to - update - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: search_for_response_policy_ids - label: Response Policies - Search for Response Policies in your environment by providing an FQL filter - with sort andor paging details This returns a set of Response Policy IDs that - match the given criteria - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The filter expression that should be used to determine the results. - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The offset of the first record to retrieve from - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The maximum number of records to return [1-5000] - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The property to sort results by - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: search_for_response_policies - label: Response Policies - Search for Response Policies in your environment by providing an FQL filter - and paging details Returns a set of Response Policies which match the filter criteria - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The filter expression that should be used to limit the results - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The offset to start retrieving records from - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The maximum records to return. [1-5000] - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The property to sort by - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: search_for_sensor_update_policies - label: Sensor Update Policies - Search for Sensor Update Policies in your environment by providing an FQL - filter and paging details Returns a set of Sensor Update Policies which match - the filter criteria - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The filter expression that should be used to limit the results - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The offset to start retrieving records from - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The maximum records to return. [1-5000] - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The property to sort by - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: search_for_sensor_update_policy_member_ids - label: Sensor Update Policies - Search for members of a Sensor Update Policy in your environment by providing - an FQL filter and paging details Returns a set of Agent IDs which match the filter - criteria - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The ID of the Sensor Update Policy to search for members of - name: id - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The filter expression that should be used to limit the results - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The offset to start retrieving records from - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The maximum records to return. [1-5000] - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The property to sort by - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: perform_action_on_the_sensor_update_policies - label: Sensor Update Policies - Perform the specified action on the Sensor Update Policies specified in the - request - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The action to perform - name: action_name - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: search_for_sensor_update_policy_members - label: Sensor Update Policies - Search for members of a Sensor Update Policy in your environment by providing - an FQL filter and paging details Returns a set of host details which match the - filter criteria - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The ID of the Sensor Update Policy to search for members of - name: id - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The filter expression that should be used to limit the results - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The offset to start retrieving records from - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The maximum records to return. [1-5000] - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The property to sort by - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: retrieve_available_builds_for_use_with_sensor_update_policies - label: Sensor Update Policies - Retrieve available builds for use with Sensor Update Policies - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The platform to return builds for - name: platform - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: search_for_sensor_update_policy_ids - label: Sensor Update Policies - Search for Sensor Update Policies in your environment by providing an FQL - filter and paging details Returns a set of Sensor Update Policy IDs which match - the filter criteria - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The filter expression that should be used to limit the results - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The offset to start retrieving records from - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The maximum records to return. [1-5000] - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The property to sort by - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: search_for_sensor_update_policies_with_additional_support_for_uninstall_protection - label: Sensor Update Policies - Search for Sensor Update Policies with additional support for uninstall protection - in your environment by providing an FQL filter and paging details Returns a set - of Sensor Update Policies which match the filter criteria - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The filter expression that should be used to limit the results - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The offset to start retrieving records from - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The maximum records to return. [1-5000] - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The property to sort by - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: retrieve_a_set_of_sensor_update_policies_with_additional_support_for_uninstall_protection - label: Sensor Update Policies - Retrieve a set of Sensor Update Policies with additional support for uninstall - protection by specifying their IDs - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The IDs of the Sensor Update Policies to return - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: create_sensor_update_policies - label: Sensor Update Policies - Create Sensor Update Policies by specifying details about the policy to create - with additional support for uninstall protection - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: update_sensor_update_policies - label: Sensor Update Policies - Update Sensor Update Policies by specifying the ID of the policy and details - to update with additional support for uninstall protection - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: retrieve_an_uninstall_token_for_a_specific_device - label: Sensor Update Policies - Reveals an uninstall token for a specific device To retrieve the bulk maintenance - token pass the value MAINTENANCE as the value for device_id - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: set_precedence_of_sensor_update_policies - label: Sensor Update Policies - Sets the precedence of Sensor Update Policies based on the order of IDs specified - in the request The first ID specified will have the highest precedence and the - last ID specified will have the lowest You must specify all nonDefault Policies - for a platform when updating precedence - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: retrieve_a_set_of_sensor_update_policies - label: Sensor Update Policies - Retrieve a set of Sensor Update Policies by specifying their IDs - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The IDs of the Sensor Update Policies to return - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: delete_a_set_of_sensor_update_policies - label: Sensor Update Policies - Delete a set of Sensor Update Policies by specifying their IDs - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The IDs of the Sensor Update Policies to delete - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: create_sensor_update_policies - label: Sensor Update Policies - Create Sensor Update Policies by specifying details about the policy to create - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: update_sensor_update_policies - label: Sensor Update Policies - Update Sensor Update Policies by specifying the ID of the policy and details - to update - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_a_set_of_ioa_exclusions - label: IOA Exclusions - Get a set of IOA Exclusions by specifying their IDs - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The ids of the exclusions to retrieve - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: delete_the_ioa_exclusions_by_id - label: IOA Exclusions - Delete the IOA exclusions by id - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The ids of the exclusions to delete - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Explains why this exclusions was deleted - name: comment - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: create_the_ioa_exclusions - label: IOA Exclusions - Create the IOA exclusions - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: update_the_ioa_exclusions - label: IOA Exclusions - Update the IOA exclusions - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: search_for_ioa_exclusions - label: IOA Exclusions - Search for IOA exclusions - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The filter expression that should be used to limit the results. - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The offset to start retrieving records from - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The maximum records to return. [1-500] - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The sort expression that should be used to sort the results. - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: search_for_ml_exclusions - label: ML Exclusions - Search for ML exclusions - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The filter expression that should be used to limit the results. - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The offset to start retrieving records from - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The maximum records to return. [1-500] - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The sort expression that should be used to sort the results. - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_a_set_of_ml_exclusions - label: ML Exclusions - Get a set of ML Exclusions by specifying their IDs - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The ids of the exclusions to retrieve - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: delete_the_ml_exclusions_by_id - label: ML Exclusions - Delete the ML exclusions by id - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The ids of the exclusions to delete - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Explains why this exclusions was deleted - name: comment - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: create_the_ml_exclusions - label: ML Exclusions - Create the ML exclusions - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: update_the_ml_exclusions - label: ML Exclusions - Update the ML exclusions - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_a_set_of_sensor_visibility_exclusions - label: Sensor Visibility Exclusions - Get a set of Sensor Visibility Exclusions by specifying their IDs - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The ids of the exclusions to retrieve - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: delete_the_sensor_visibility_exclusions_by_id - label: Sensor Visibility Exclusions - Delete the sensor visibility exclusions by id - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The ids of the exclusions to delete - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Explains why this exclusions was deleted - name: comment - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: create_the_sensor_visibility_exclusions - label: Sensor Visibility Exclusions - Create the sensor visibility exclusions - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: update_the_sensor_visibility_exclusions - label: Sensor Visibility Exclusions - Update the sensor visibility exclusions - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: search_for_sensor_visibility_exclusions - label: Sensor Visibility Exclusions - Search for sensor visibility exclusions - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The filter expression that should be used to limit the results. - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The offset to start retrieving records from - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The maximum records to return. [1-500] - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The sort expression that should be used to sort the results. - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_status_of_an_executed_active_responder_command_on_a_single_host - label: Real Time Response - Get status of an executed active_responder command on a single host - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Cloud Request ID of the executed command to query - name: cloud_request_id - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Sequence ID that we want to retrieve. Command responses are chunked - across sequences - name: sequence_id - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: execute_an_active_responder_command_on_a_single_host - label: Real Time Response - Execute an active responder command on a single host - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: batch_refresh_a_rtr_session_on_multiple_hosts_rtr_sessions_will_expire_after_10_minutes_unless_refreshed - label: Real Time Response - Batch refresh a RTR session on multiple hosts RTR sessions will expire after - 10 minutes unless refreshed - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Timeout for how long to wait for the request in seconds, default - timeout is 30 seconds. Maximum is 10 minutes. - name: timeout - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: 'Timeout duration for for how long to wait for the request in duration - syntax. Example, `10s`. Valid units: `ns, us, ms, s, m, h`. Maximum is 10 minutes.' - name: timeout_duration - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_queued_session_metadata_by_session_id - label: Real Time Response - Get queued session metadata by session ID - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: refresh_a_session_timeout_on_a_single_host - label: Real Time Response - Refresh a session timeout on a single host - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: batch_initialize_a_rtr_session_on_multiple_hosts__before_any_rtr_commands_can_be_used_an_active_session_is_needed_on_the_host - label: Real Time Response - Batch initialize a RTR session on multiple hosts Before any RTR commands - can be used an active session is needed on the host - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Timeout for how long to wait for the request in seconds, default - timeout is 30 seconds. Maximum is 10 minutes. - name: timeout - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: 'Timeout duration for for how long to wait for the request in duration - syntax. Example, `10s`. Valid units: `ns, us, ms, s, m, h`. Maximum is 10 minutes.' - name: timeout_duration - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_rtr_extracted_file_contents_for_specified_session_and_sha256 - label: Real Time Response - Get RTR extracted file contents for specified session and sha256 - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: RTR Session id - name: session_id - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Extracted SHA256 (e.g. 'efa256a96af3b556cd3fc9d8b1cf587d72807d7805ced441e8149fc279db422b') - name: sha256 - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Filename to use for the archive name and the file within the archive. - name: filename - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_aggregates_on_session_data - label: Real Time Response - Get aggregates on session data - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: delete_a_session - label: Real Time Response - Delete a session - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: RTR Session id - name: session_id - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: initialize_a_new_session_with_the_rtr_cloud - label: Real Time Response - Initialize a new session with the RTR cloud - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: delete_a_queued_session_command - label: Real Time Response - Delete a queued session command - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: RTR Session id - name: session_id - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Cloud Request ID of the executed command to query - name: cloud_request_id - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_a_list_of_session_ids - label: Real Time Response - Get a list of session_ids - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Starting index of overall result set from which to return ids. - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Number of ids to return. - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: 'Sort by spec. Ex: ''date_created|asc''.' - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Optional filter criteria in the form of an FQL query. For more information - about FQL queries, see our [FQL documentation in Falcon](https://falcon.crowdstrike.com/support/documentation/45/falcon-query-language-feature-guide). - "user_id" can accept a special value '@me' which will restrict results to records - with current user's ID. - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: retrieve_the_status_of_batch_get_command__will_return_successful_files_when_they_are_finished_processing - label: Real Time Response - retrieve the status of the specified batch get command Will return successful - files when they are finished processing - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Batch Get Command Request ID received from `/real-time-response/combined/get-command/v1` - name: batch_get_cmd_req_id - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Timeout for how long to wait for the request in seconds, default - timeout is 30 seconds. Maximum is 10 minutes. - name: timeout - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: 'Timeout duration for for how long to wait for the request in duration - syntax. Example, `10s`. Valid units: `ns, us, ms, s, m, h`. Maximum is 10 minutes.' - name: timeout_duration - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: batch_executes_get_command_across_hosts_to_retrieve_files_after_this_call_is_made_get_realtimeresponsecombinedbatchgetcommandv1_is_used_to_query_for_the_results - label: Real Time Response - Batch executes get command across hosts to retrieve files After this call - is made GET realtimeresponsecombinedbatchgetcommandv1 is used to query for the - results - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Timeout for how long to wait for the request in seconds, default - timeout is 30 seconds. Maximum is 10 minutes. - name: timeout - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: 'Timeout duration for for how long to wait for the request in duration - syntax. Example, `10s`. Valid units: `ns, us, ms, s, m, h`. Maximum is 10 minutes.' - name: timeout_duration - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: batch_executes_a_rtr_readonly_command - label: Real Time Response - Batch executes a RTR readonly command across the hosts mapped to the given - batch ID - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Timeout for how long to wait for the request in seconds, default - timeout is 30 seconds. Maximum is 10 minutes. - name: timeout - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: 'Timeout duration for for how long to wait for the request in duration - syntax. Example, `10s`. Valid units: `ns, us, ms, s, m, h`. Maximum is 10 minutes.' - name: timeout_duration - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_session_metadata_by_session_id - label: Real Time Response - Get session metadata by session id - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_a_list_of_files_for_rtr_session - label: Real Time Response - Get a list of files for the specified RTR session - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: RTR Session id - name: session_id - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: delete_a_rtr_session_file - label: Real Time Response - Delete a RTR session file - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: RTR Session file id - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: RTR Session id - name: session_id - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_status_of_an_executed_command_on_a_single_host - label: Real Time Response - Get status of an executed command on a single host - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Cloud Request ID of the executed command to query - name: cloud_request_id - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Sequence ID that we want to retrieve. Command responses are chunked - across sequences - name: sequence_id - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: execute_a_command_on_a_single_host - label: Real Time Response - Execute a command on a single host - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: batch_executes_a_rtr_active_responder_command - label: Real Time Response - Batch executes a RTR active_responder command across the hosts mapped to the - given batch ID - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Timeout for how long to wait for the request in seconds, default - timeout is 30 seconds. Maximum is 10 minutes. - name: timeout - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: 'Timeout duration for for how long to wait for the request in duration - syntax. Example, `10s`. Valid units: `ns, us, ms, s, m, h`. Maximum is 10 minutes.' - name: timeout_duration - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_putfiles_based_on_the_ids_given - label: Real Time Response Admin - Get putfiles based on the IDs given These are used for the RTR put command - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: File IDs - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: delete_a_putfile_based_on_the_ids_given - label: Real Time Response Admin - Delete a putfile based on the ID given Can only delete one file at a time - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: File id - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: upload_a_new_putfile_to_use_for_the_rtr_put_command - label: Real Time Response Admin - Upload a new putfile to use for the RTR put command - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_status_of_an_executed_rtr_administrator_command_on_a_single_host - label: Real Time Response Admin - Get status of an executed RTR administrator command on a single host - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Cloud Request ID of the executed command to query - name: cloud_request_id - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Sequence ID that we want to retrieve. Command responses are chunked - across sequences - name: sequence_id - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: execute_a_rtr_administrator_command_on_a_single_host - label: Real Time Response Admin - Execute a RTR administrator command on a single host - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_a_list_of_putfile_ids - label: Real Time Response Admin - Get a list of putfile IDs that are available to the user for the put command - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Optional filter criteria in the form of an FQL query. For more information - about FQL queries, see our [FQL documentation in Falcon](https://falcon.crowdstrike.com/support/documentation/45/falcon-query-language-feature-guide). - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Starting index of overall result set from which to return ids. - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Number of ids to return. - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: 'Sort by spec. Ex: ''created_at|asc''.' - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_a_list_of_custom_script_ids - label: Real Time Response Admin - Get a list of custom_script IDs that are available to the user for the runscript - command - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Optional filter criteria in the form of an FQL query. For more information - about FQL queries, see our [FQL documentation in Falcon](https://falcon.crowdstrike.com/support/documentation/45/falcon-query-language-feature-guide). - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Starting index of overall result set from which to return ids. - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Number of ids to return. - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: 'Sort by spec. Ex: ''created_at|asc''.' - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_custom_scripts_based_on_the_ids_given - label: Real Time Response Admin - Get custom_scripts based on the IDs given These are used for the RTR runscript - command - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: File IDs - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: delete_a_custom_script_based_on_the_id_given - label: Real Time Response Admin - Delete a custom_script based on the ID given Can only delete one script at - a time - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: File id - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: upload_a_new_custom_script_to_use - label: Real Time Response Admin - Upload a new custom_script to use for the RTR runscript command - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: upload_a_new_scripts_to_replace_an_existing_one - label: Real Time Response Admin - Upload a new scripts to replace an existing one - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: batch_executes_a_rtr_administrator_command - label: Real Time Response Admin - Batch executes a RTR administrator command across the hosts mapped to the - given batch ID - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Timeout for how long to wait for the request in seconds, default - timeout is 30 seconds. Maximum is 10 minutes. - name: timeout - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: 'Timeout duration for for how long to wait for the request in duration - syntax. Example, `10s`. Valid units: `ns, us, ms, s, m, h`. Maximum is 10 minutes.' - name: timeout_duration - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: delete_notifications_based_on_ids_notifications - label: Recon - Delete notifications based on IDs Notifications cannot be recovered after - they are deleted - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Notifications IDs. - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: update_notification_status_or_assignee - label: Recon - Update notification status or assignee Accepts bulk requests - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: query_notifications - label: Recon - Query notifications based on provided criteria Use the IDs from this response - to get the notification entities on GET entitiesnotificationsv1 or GET entitiesnotificationsdetailedv1 - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Starting index of overall result set from which to return ids. - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Number of ids to return. - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: 'Possible order by fields: created_date, updated_date. Ex: ''updated_date|desc''.' - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: 'FQL query to filter notifications by. Possible filter properties - are: [id cid user_uuid status rule_id rule_name rule_topic rule_priority item_type - created_date updated_date]' - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Free text search across all indexed fields. - name: q - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_detailed_notifications_based_on_their_ids - label: Recon - Get detailed notifications based on their IDs These include the raw intelligence - content that generated the matchThis endpoint will return translated notification - content The only target language available is English A single notification can - be translated per request - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Notification IDs. - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: preview_rules_notification_count_and_distribution - label: Recon - Preview rules notification count and distribution This will return aggregations - on channel count site - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_notification_aggregates - label: Recon - Get notification aggregates - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_actions_based_on_their_ids - label: Recon - Get actions based on their IDs IDs can be retrieved using the GET queriesactionsv1 - endpoint - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Action IDs. - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: delete_an_action_from_a_monitoring_rule_based_on_the_action_id - label: Recon - Delete an action from a monitoring rule based on the action ID - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: ID of the action. - name: id - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: create_actions_for_a_monitoring_rule - label: Recon - Create actions for a monitoring rule Accepts a list of actions that will - be attached to the monitoring rule - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Generated by shuffler.io OpenAPI - name: body - example: |- - { - "actions": "${actions}", - "rule_id": "${rule_id}" - } - value: |- - { - "actions": "${actions}", - "rule_id": "${rule_id}" - } - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: update_an_action_for_a_monitoring_rule - label: Recon - Update an action for a monitoring rule - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Generated by shuffler.io OpenAPI - name: body - example: |- - { - "frequency": "${frequency}", - "id": "${id}", - "recipients": "${recipients}", - "status": "${status}" - } - value: |- - { - "frequency": "${frequency}", - "id": "${id}", - "recipients": "${recipients}", - "status": "${status}" - } - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: query_actions - label: Recon - Query actions based on provided criteria Use the IDs from this response to - get the action entities on GET entitiesactionsv1 - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Starting index of overall result set from which to return IDs. - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Number of IDs to return. - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: 'Possible order by fields: created_timestamp, updated_timestamp. - Ex: ''updated_timestamp|desc''.' - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: 'FQL query to filter actions by. Possible filter properties are: - [id cid user_uuid rule_id type frequency recipients status created_timestamp - updated_timestamp]' - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Free text search across all indexed fields - name: q - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: query_monitoring_rules - label: Recon - Query monitoring rules based on provided criteria Use the IDs from this response - to fetch the rules on entitiesrulesv1 - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Starting index of overall result set from which to return ids. - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Number of ids to return. - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: 'Possible order by fields: created_timestamp, last_updated_timestamp. - Ex: ''last_updated_timestamp|desc''.' - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: 'FQL query to filter rules by. Possible filter properties are: [id - cid user_uuid topic priority permissions filter status created_timestamp last_updated_timestamp]' - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Free text search across all indexed fields. - name: q - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_notifications_based_on_their_ids - label: Recon - Get notifications based on their IDs IDs can be retrieved using the GET queriesnotificationsv1 - endpoint This endpoint will return translated notification content The only target - language available is English - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Notification IDs. - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_detailed_notifications_based_on_their_ids_with_raw_intelligence_content_that_generated_the_match - label: Recon - Get detailed notifications based on their IDs These include the raw intelligence - content that generated the match - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Notification IDs. - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_monitoring_rules_rules_by_provided_ids - label: Recon - Get monitoring rules rules by provided IDs - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: IDs of rules. - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: delete_monitoring_rules - label: Recon - Delete monitoring rules - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: IDs of rules. - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: create_monitoring_rules - label: Recon - Create monitoring rules - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: update_monitoring_rules - label: Recon - Update monitoring rules - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_notifications_based_on_their_ids - label: Recon - Get notifications based on their IDs IDs can be retrieved using the GET queriesnotificationsv1 - endpoint - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Notification IDs. - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: upload_a_file_for_further_cloud_analysis - label: Sample Uploads - Upload a file for further cloud analysis After uploading call the specific - analysis API endpoint - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Name of the file. - name: file_name - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: A descriptive comment to identify the file for other users. - name: comment - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: "Defines visibility of this file in Falcon MalQuery, either via the - API or the Falcon console.\n\n- `true`: File is only shown to users within your - customer account\n- `false`: File can be seen by other CrowdStrike customers - \n\nDefault: `true`." - name: is_confidential - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: retrieve_the_file_associated_with_the_given_id_sha256 - label: Sample Uploads - retrieve the file associated with the given ID SHA256 - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The file SHA256. - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Flag whether the sample should be zipped and password protected with - pass='infected' - name: password_protected - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: find_ids_for_submitted_scans - label: Quick Scan - Find IDs for submitted scans by providing an FQL filter and paging details - Returns a set of volume IDs that match your criteria - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Optional filter and sort criteria in the form of an FQL query. For - more information about FQL queries, see [our FQL documentation in Falcon](https://falcon.crowdstrike.com/support/documentation/45/falcon-query-language-feature-guide). - name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The offset to start retrieving submissions from. - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: 'Maximum number of volume IDs to return. Max: 5000.' - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: 'Sort order: `asc` or `desc`.' - name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_scans_aggregations - label: Quick Scan - Get scans aggregations - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Generated by shuffler.io OpenAPI - name: body - example: |- - { - "date_ranges": "${date_ranges}", - "field": "${field}", - "filter": "${filter}", - "interval": "${interval}", - "min_doc_count": "${min_doc_count}", - "missing": "${missing}", - "name": "${name}", - "q": "${q}", - "ranges": "${ranges}", - "size": "${size}", - "sort": "${sort}", - "sub_aggregates": "${sub_aggregates}", - "time_zone": "${time_zone}", - "type": "${type}" - } - value: |- - { - "date_ranges": "${date_ranges}", - "field": "${field}", - "filter": "${filter}", - "interval": "${interval}", - "min_doc_count": "${min_doc_count}", - "missing": "${missing}", - "name": "${name}", - "q": "${q}", - "ranges": "${ranges}", - "size": "${size}", - "sort": "${sort}", - "sub_aggregates": "${sub_aggregates}", - "time_zone": "${time_zone}", - "type": "${type}" - } - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: check_the_status_of_a_volume_scan - label: Quick Scan - Check the status of a volume scan Time required for analysis increases with - the number of samples in a volume but usually it should take less than 1 minute - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: ID of a submitted scan - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: submit_a_volume_of_files_for_ml_scanning - label: Quick Scan - Submit a volume of files for ml scanning Time required for analysis increases - with the number of samples in a volume but usually it should take less than 1 - minute - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_sensor_installer_ids_by_provided_query - label: Sensor Download - Get sensor installer IDs by provided query - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The first item to return, where 0 is the latest item. Use with the - limit parameter to manage pagination of results. - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: 'The number of items to return in this response (default: 100, max: - 500). Use with the offset parameter to manage pagination of results.' - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: |- - Sort items using their properties. Common sort options include: - -
  • version|asc
  • release_date|desc
- name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: |- - Filter items using a query in Falcon Query Language (FQL). An asterisk wildcard * includes all results. - - Common filter options include: -
  • platform:"windows"
  • version:>"5.2"
- name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_sensor_installer_details_by_provided_query - label: Sensor Download - Get sensor installer details by provided query - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: The first item to return, where 0 is the latest item. Use with the - limit parameter to manage pagination of results. - name: offset - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: 'The number of items to return in this response (default: 100, max: - 500). Use with the offset parameter to manage pagination of results.' - name: limit - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: |- - Sort items using their properties. Common sort options include: - -
  • version|asc
  • release_date|desc
- name: sort - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: |- - Filter items using a query in Falcon Query Language (FQL). An asterisk wildcard * includes all results. - - Common filter options include: -
  • platform:"windows"
  • version:>"5.2"
- name: filter - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_sensor_installer_details_by_provided_sha256_ids - label: Sensor Download - Get sensor installer details by provided SHA256 IDs - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: The IDs of the installers - name: ids - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: download_sensor_installer_by_sha256_id - label: Sensor Download - Download sensor installer by SHA256 ID - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: SHA256 of the installer to download - name: id - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_ccid_to_use_with_sensor_installers - label: Sensor Download - Get CCID to use with sensor installers - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: refresh_an_active_event_stream - label: Event Streams - Refresh an active event stream Use the URL shown in a GET sensorsentitiesdatafeedv2 - response - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: Action name. Allowed value is refresh_active_stream_session. - name: action_name - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: 'Label that identifies your connection. Max: 32 alphanumeric characters - (a-z, A-Z, 0-9).' - name: appId - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Generated by shuffler.io OpenAPI - name: partition - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -- description: "" - name: get_all_event_streams - label: Event Streams - Discover all event streams in your environment - nodetype: action - environment: Shuffle - sharing: false - privateid: "" - publicid: "" - appid: "" - tags: [] - tested: false - parameters: - - description: 'Label that identifies your connection. Max: 32 alphanumeric characters - (a-z, A-Z, 0-9).' - name: appId - example: "" - multiline: false - options: [] - required: true - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit headers - name: headers - example: "" - value: |- - Authorization: Bearer $auth.access_token - Accept-Encoding: application/json - Content-Type: application/json - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: Add or edit queries - name: queries - example: view=basic&redirect=test - multiline: true - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - - description: 'Format for streaming events. Valid values: json, flatjson' - name: format - example: "" - multiline: false - options: [] - required: false - configuration: false - tags: [] - schema: - type: string - skip_multicheck: false - unique_toggled: false - executionvariable: - description: "" - id: "" - name: "" - value: "" - returns: - example: "" - schema: - type: string - authenticationid: "" - example: "" - auth_not_required: false - source_workflow: "" -authentication: - required: true - parameters: - - description: "" - id: "" - name: client_id - example: '******' - value: "" - multiline: false - required: true - in: "" - schema: - type: string - scheme: "" - - description: "" - id: "" - name: client_secret - example: '******' - value: "" - multiline: false - required: true - in: "" - schema: - type: string - scheme: "" - - description: The URL of the app - id: "" - name: url - example: https://api.crowdstrike.com - value: https://api.crowdstrike.com - multiline: false - required: true - in: "" - schema: - type: string - scheme: "" -tags: [] -categories: [] -created: 0 -edited: 0 -lastruntime: 0 -versions: [] -loopversions: [] -owner: b5ee0878-2de4-4182-92af-bf67ec6526f5 -public: false -referenceorg: "" -referenceurl: "" -large_image: data:image/jpeg;base64,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 diff --git a/crowdstrike-falcon/1.0.0/requirements.txt b/crowdstrike-falcon/1.0.0/requirements.txt deleted file mode 100644 index f76ae497..00000000 --- a/crowdstrike-falcon/1.0.0/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -# No extra requirements needed diff --git a/crowdstrike-falcon/1.0.0/src/app.py b/crowdstrike-falcon/1.0.0/src/app.py deleted file mode 100755 index 376d9ff3..00000000 --- a/crowdstrike-falcon/1.0.0/src/app.py +++ /dev/null @@ -1,3749 +0,0 @@ -import requests -import asyncio -import json -import urllib3 - -from walkoff_app_sdk.app_base import AppBase - -class Crowdstrike_Falcon(AppBase): - - __version__ = "1.0" - app_name = "Crowdstrike_Falcon" - - - def __init__(self, redis, logger, console_logger=None): - self.verify = False - urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) - super().__init__(redis, logger, console_logger) - - - def setup_headers(self, headers): - request_headers={} - - if len(headers) > 0: - for header in headers.split("\n"): - if '=' in header: - headersplit=header.split('=') - request_headers[headersplit[0].strip()] = headersplit[1].strip() - elif ':' in header: - headersplit=header.split(':') - request_headers[headersplit[0].strip()] = headersplit[1].strip() - return request_headers - - - def setup_params(self, queries): - params={} - - if len(queries) > 0: - for query in queries.split("\&"): - if '=' in query: - headersplit=query.split('&') - params[headersplit[0].strip()] = headersplit[1].strip() - - return params - - - def generate_oauth2_access_token(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/oauth2/token" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - body={'client_id': client_id, 'client_secret': client_secret} - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def revoke_oauth2_access_token(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/oauth2/revoke" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - body={'client_id': client_id, 'client_secret': client_secret} - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def download_analysis_artifacts(self, url, client_id, client_secret, id, headers="", queries="", name=""): - params={} - request_headers={} - url=f"{url}/falconx/entities/artifacts/v1?id={id}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - - if name: - params["name"] = name - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_detect_aggregates(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={"Content-Type": "application/json","Accept": "application/json"} - url=f"{url}/detects/aggregates/detects/GET/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def view_information_about_detections(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={"Content-Type": "application/json","Accept": "application/json"} - url=f"{url}/detects/entities/summaries/GET/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def modify_detections(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={"Content-Type": "application/json","Accept": "application/json"} - url=f"{url}/detects/entities/detects/v2" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.patch(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_sandbox_reports(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): - params={} - request_headers={} - url=f"{url}/falconx/queries/reports/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_rules_by_id(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/ioarules/entities/rules/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def delete_rules_from_a_rule_group_by_id(self, url, client_id, client_secret, rule_group_id, ids, headers="", queries="", comment=""): - params={} - request_headers={} - url=f"{url}/ioarules/entities/rules/v1?rule_group_id={rule_group_id}&ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.delete(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def create_a_rule_within_a_rule_group(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={"Content-Type": "application/json","Accept": "application/json"} - url=f"{url}/ioarules/entities/rules/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def update_rules_within_a_rule_group(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={"Content-Type": "application/json","Accept": "application/json"} - url=f"{url}/ioarules/entities/rules/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.patch(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def search_for_prevention_policy_members(self, url, client_id, client_secret, headers="", queries="", id="", filter="", offset="", limit="", sort=""): - params={} - request_headers={} - url=f"{url}/policy/combined/prevention-members/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if filter: - params["filter"] = filter - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def set_precedence_of_device_control_policies(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/policy/entities/device-control-precedence/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def retrieve_hidden_hosts(self, url, client_id, client_secret, headers="", queries="", offset="", limit="", sort="", filter=""): - params={} - request_headers={} - url=f"{url}/devices/queries/devices-hidden/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - if filter: - params["filter"] = filter - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_rule_types_by_id(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/ioarules/entities/rule-types/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_all_platform_ids(self, url, client_id, client_secret, headers="", queries="", offset="", limit=""): - params={} - request_headers={} - url=f"{url}/ioarules/queries/platforms/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if limit: - params["limit"] = limit - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_combined_for_indicators(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): - params={} - request_headers={} - url=f"{url}/iocs/combined/indicator/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def set_precedence_of_response_policies(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/policy/entities/response-precedence/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_a_set_of_sensor_visibility_exclusions(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/policy/entities/sv-exclusions/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def delete_the_sensor_visibility_exclusions_by_id(self, url, client_id, client_secret, ids, headers="", queries="", comment=""): - params={} - request_headers={} - url=f"{url}/policy/entities/sv-exclusions/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.delete(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def create_the_sensor_visibility_exclusions(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/policy/entities/sv-exclusions/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def update_the_sensor_visibility_exclusions(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/policy/entities/sv-exclusions/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.patch(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def search_for_prevention_policy_ids(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): - params={} - request_headers={} - url=f"{url}/policy/queries/prevention/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_notifications_based_on_their_ids(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/recon/entities/notifications/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def delete_notifications_based_on_ids_notifications(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/recon/entities/notifications/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.delete(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def update_notification_status_or_assignee(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={"Content-Type": "application/json","Accept": "application/json"} - url=f"{url}/recon/entities/notifications/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.patch(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_sensor_installer_ids_by_provided_query(self, url, client_id, client_secret, headers="", queries="", offset="", limit="", sort="", filter=""): - params={} - request_headers={} - url=f"{url}/sensors/queries/installers/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - if filter: - params["filter"] = filter - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_info_about_indicators(self, url, client_id, client_secret, headers="", queries="", offset="", limit="", sort="", filter="", q="", include_deleted=""): - params={} - request_headers={} - url=f"{url}/intel/combined/indicators/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - if filter: - params["filter"] = filter - if q: - params["q"] = q - if include_deleted: - params["include_deleted"] = include_deleted - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def download_earlier_rule_sets(self, url, client_id, client_secret, id, headers="", queries="", format=""): - params={} - request_headers={"Accept": "undefined"} - url=f"{url}/intel/entities/rules-files/v1?id={id}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_report_ids(self, url, client_id, client_secret, headers="", queries="", offset="", limit="", sort="", filter="", q=""): - params={} - request_headers={} - url=f"{url}/intel/queries/reports/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - if filter: - params["filter"] = filter - if q: - params["q"] = q - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def search_for_rule_ids(self, url, client_id, client_secret, type, headers="", queries="", offset="", limit="", sort="", name="", description="", tags="", min_created_date="", max_created_date="", q=""): - params={} - request_headers={} - url=f"{url}/intel/queries/rules/v1?type={type}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - if name: - params["name"] = name - if description: - params["description"] = description - if tags: - params["tags"] = tags - if min_created_date: - params["min_created_date"] = min_created_date - if max_created_date: - params["max_created_date"] = max_created_date - if q: - params["q"] = q - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def search_for_sensor_update_policies(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): - params={} - request_headers={} - url=f"{url}/policy/combined/sensor-update/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_a_set_of_ioa_exclusions(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/policy/entities/ioa-exclusions/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def delete_the_ioa_exclusions_by_id(self, url, client_id, client_secret, ids, headers="", queries="", comment=""): - params={} - request_headers={} - url=f"{url}/policy/entities/ioa-exclusions/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.delete(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def create_the_ioa_exclusions(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/policy/entities/ioa-exclusions/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def update_the_ioa_exclusions(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/policy/entities/ioa-exclusions/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.patch(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def search_for_sensor_update_policy_member_ids(self, url, client_id, client_secret, headers="", queries="", id="", filter="", offset="", limit="", sort=""): - params={} - request_headers={} - url=f"{url}/policy/queries/sensor-update-members/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if filter: - params["filter"] = filter - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def search_for_sensor_visibility_exclusions(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): - params={} - request_headers={} - url=f"{url}/policy/queries/sv-exclusions/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def find_ids_for_submitted_scans(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): - params={} - request_headers={} - url=f"{url}/scanner/queries/scans/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_sensor_installer_details_by_provided_query(self, url, client_id, client_secret, headers="", queries="", offset="", limit="", sort="", filter=""): - params={} - request_headers={} - url=f"{url}/sensors/combined/installers/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - if filter: - params["filter"] = filter - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def search_for_hosts(self, url, client_id, client_secret, headers="", queries="", offset="", limit="", sort="", filter=""): - params={} - request_headers={} - url=f"{url}/devices/queries/devices-scroll/v1" - - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - if filter: - params["filter"] = filter - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_info_about_reports(self, url, client_id, client_secret, headers="", queries="", offset="", limit="", sort="", filter="", q="", fields=""): - params={} - request_headers={} - url=f"{url}/intel/combined/reports/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - if filter: - params["filter"] = filter - if q: - params["q"] = q - if fields: - params["fields"] = fields - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_a_zipped_sample(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/malquery/entities/samples-fetch/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def schedule_samples_for_download(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={"Content-Type": "application/json","Accept": "application/json"} - url=f"{url}/malquery/entities/samples-multidownload/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def perform_action_on_the_sensor_update_policies(self, url, client_id, client_secret, action_name, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/policy/entities/sensor-update-actions/v1?action_name={action_name}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def query_notifications(self, url, client_id, client_secret, headers="", queries="", offset="", limit="", sort="", filter="", q=""): - params={} - request_headers={} - url=f"{url}/recon/queries/notifications/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - if filter: - params["filter"] = filter - if q: - params["q"] = q - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def search_for_prevention_policies(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): - params={} - request_headers={} - url=f"{url}/policy/combined/prevention/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_status_of_an_executed_active_responder_command_on_a_single_host(self, url, client_id, client_secret, cloud_request_id, sequence_id, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/real-time-response/entities/active-responder-command/v1?cloud_request_id={cloud_request_id}&sequence_id={sequence_id}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def execute_an_active_responder_command_on_a_single_host(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/real-time-response/entities/active-responder-command/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def find_all_rule_ids(self, url, client_id, client_secret, headers="", queries="", sort="", filter="", q="", offset="", limit=""): - params={} - request_headers={} - url=f"{url}/ioarules/queries/rules/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if filter: - params["filter"] = filter - if q: - params["q"] = q - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def set_precedence_of_prevention_policies(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/policy/entities/prevention-precedence/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_indicators_ids(self, url, client_id, client_secret, headers="", queries="", offset="", limit="", sort="", filter="", q="", include_deleted=""): - params={} - request_headers={} - url=f"{url}/intel/queries/indicators/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - if filter: - params["filter"] = filter - if q: - params["q"] = q - if include_deleted: - params["include_deleted"] = include_deleted - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def search_for_sensor_update_policy_members(self, url, client_id, client_secret, headers="", queries="", id="", filter="", offset="", limit="", sort=""): - params={} - request_headers={} - url=f"{url}/policy/combined/sensor-update-members/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if filter: - params["filter"] = filter - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def batch_refresh_a_rtr_session_on_multiple_hosts_rtr_sessions_will_expire_after_10_minutes_unless_refreshed(self, url, client_id, client_secret, headers="", queries="", timeout="", timeout_duration="", body=""): - params={} - request_headers={} - url=f"{url}/real-time-response/combined/batch-refresh-session/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if timeout_duration: - params["timeout_duration"] = timeout_duration - body = " ".join(body.strip().split()).encode("utf-8") - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_queued_session_metadata_by_session_id(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/real-time-response/entities/queued-sessions/GET/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def perform_action_on_the_device_control_policies(self, url, client_id, client_secret, action_name, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/policy/entities/device-control-actions/v1?action_name={action_name}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_scans_aggregations(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={"Content-Type": "application/json","Accept": "application/json"} - url=f"{url}/scanner/aggregates/scans/GET/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_detailed_notifications_based_on_their_ids(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/recon/entities/notifications-detailed-translated/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def retrieve_specific_indicators_using_their_indicator_ids(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={"Content-Type": "application/json","Accept": "application/json"} - url=f"{url}/intel/entities/indicators/GET/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def find_all_rule_group_ids(self, url, client_id, client_secret, headers="", queries="", sort="", filter="", q="", offset="", limit=""): - params={} - request_headers={} - url=f"{url}/ioarules/queries/rule-groups/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if filter: - params["filter"] = filter - if q: - params["q"] = q - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def search_falcon_malquery(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={"Content-Type": "application/json","Accept": "application/json"} - url=f"{url}/malquery/queries/exact-search/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def retrieve_available_builds_for_use_with_sensor_update_policies(self, url, client_id, client_secret, headers="", queries="", platform=""): - params={} - request_headers={} - url=f"{url}/policy/combined/sensor-update-builds/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def search_for_firewall_policies(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): - params={} - request_headers={} - url=f"{url}/policy/queries/firewall/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def retrieve_set_of_host_groups(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/devices/entities/host-groups/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def delete_set_of_host_groups(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/devices/entities/host-groups/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.delete(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def create_host_groups(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/devices/entities/host-groups/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def update_host_groups(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/devices/entities/host-groups/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.patch(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def search_for_behaviors(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): - params={} - request_headers={} - url=f"{url}/incidents/queries/behaviors/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def search_for_incidents(self, url, client_id, client_secret, headers="", queries="", sort="", filter="", offset="", limit=""): - params={} - request_headers={} - url=f"{url}/incidents/queries/incidents/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if filter: - params["filter"] = filter - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_rule_groups_by_id(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/ioarules/entities/rule-groups/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def delete_rule_groups_by_id(self, url, client_id, client_secret, ids, headers="", queries="", comment=""): - params={} - request_headers={} - url=f"{url}/ioarules/entities/rule-groups/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.delete(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def create_a_rule_group(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={"Content-Type": "application/json","Accept": "application/json"} - url=f"{url}/ioarules/entities/rule-groups/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def update_a_rule_group(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={"Content-Type": "application/json","Accept": "application/json"} - url=f"{url}/ioarules/entities/rule-groups/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.patch(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_all_rule_type_ids(self, url, client_id, client_secret, headers="", queries="", offset="", limit=""): - params={} - request_headers={} - url=f"{url}/ioarules/queries/rule-types/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if limit: - params["limit"] = limit - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_information_about_search_and_download_quotas(self, url, client_id, client_secret, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/malquery/aggregates/quotas/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def refresh_a_session_timeout_on_a_single_host(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/real-time-response/entities/refresh-session/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def query_crowdscore(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): - params={} - request_headers={} - url=f"{url}/incidents/combined/crowdscores/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def perform_actions_on_incidents(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={"Content-Type": "application/json","Accept": "application/json"} - url=f"{url}/incidents/entities/incident-actions/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_info_about_actors(self, url, client_id, client_secret, headers="", queries="", offset="", limit="", sort="", filter="", q="", fields=""): - params={} - request_headers={} - url=f"{url}/intel/combined/actors/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - if filter: - params["filter"] = filter - if q: - params["q"] = q - if fields: - params["fields"] = fields - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def search_for_response_policy_members(self, url, client_id, client_secret, headers="", queries="", id="", filter="", offset="", limit="", sort=""): - params={} - request_headers={} - url=f"{url}/policy/combined/response-members/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if filter: - params["filter"] = filter - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def batch_initialize_a_rtr_session_on_multiple_hosts__before_any_rtr_commands_can_be_used_an_active_session_is_needed_on_the_host(self, url, client_id, client_secret, headers="", queries="", timeout="", timeout_duration="", body=""): - params={} - request_headers={} - url=f"{url}/real-time-response/combined/batch-init-session/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if timeout_duration: - params["timeout_duration"] = timeout_duration - body = " ".join(body.strip().split()).encode("utf-8") - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_rtr_extracted_file_contents_for_specified_session_and_sha256(self, url, client_id, client_secret, session_id, sha256, headers="", queries="", filename=""): - params={} - request_headers={} - url=f"{url}/real-time-response/entities/extracted-file-contents/v1?session_id={session_id}&sha256={sha256}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def search_for_host_groups(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): - params={} - request_headers={} - url=f"{url}/devices/combined/host-groups/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_all_pattern_severity_ids(self, url, client_id, client_secret, headers="", queries="", offset="", limit=""): - params={} - request_headers={} - url=f"{url}/ioarules/queries/pattern-severities/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if limit: - params["limit"] = limit - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_indicators_by_ids(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/iocs/entities/indicators/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def delete_indicators_by_ids(self, url, client_id, client_secret, headers="", queries="", filter="", ids="", comment=""): - params={} - request_headers={} - url=f"{url}/iocs/entities/indicators/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if ids: - params["ids"] = ids - if comment: - params["comment"] = comment - - ret = requests.delete(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def create_indicators(self, url, client_id, client_secret, headers="", queries="", retrodetects="", ignore_warnings="", body=""): - params={} - request_headers={"Content-Type": "application/json","Accept": "application/jsonX-CS-USERNAME"} - url=f"{url}/iocs/entities/indicators/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if ignore_warnings: - params["ignore_warnings"] = ignore_warnings - body = " ".join(body.strip().split()).encode("utf-8") - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def update_indicators(self, url, client_id, client_secret, headers="", queries="", retrodetects="", ignore_warnings="", body=""): - params={} - request_headers={"Content-Type": "application/json","Accept": "application/jsonX-CS-USERNAME"} - url=f"{url}/iocs/entities/indicators/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if ignore_warnings: - params["ignore_warnings"] = ignore_warnings - body = " ".join(body.strip().split()).encode("utf-8") - ret = requests.patch(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def retrieve_a_set_of_device_control_policies(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/policy/entities/device-control/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def delete_a_set_of_device_control_policies(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/policy/entities/device-control/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.delete(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def create_device_control_policies(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/policy/entities/device-control/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def update_device_control_policies(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/policy/entities/device-control/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.patch(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def search_for_ioa_exclusions(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): - params={} - request_headers={} - url=f"{url}/policy/queries/ioa-exclusions/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_aggregates_on_session_data(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/real-time-response/aggregates/sessions/GET/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def delete_a_session(self, url, client_id, client_secret, session_id, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/real-time-response/entities/sessions/v1?session_id={session_id}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.delete(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def initialize_a_new_session_with_the_rtr_cloud(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/real-time-response/entities/sessions/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_a_full_sandbox_report(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/falconx/entities/reports/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def delete_report(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/falconx/entities/reports/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.delete(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def search_for_ml_exclusions(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): - params={} - request_headers={} - url=f"{url}/policy/queries/ml-exclusions/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def search_for_sensor_update_policy_ids(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): - params={} - request_headers={} - url=f"{url}/policy/queries/sensor-update/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def delete_a_queued_session_command(self, url, client_id, client_secret, session_id, cloud_request_id, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/real-time-response/entities/queued-sessions/command/v1?session_id={session_id}&cloud_request_id={cloud_request_id}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.delete(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def preview_rules_notification_count_and_distribution(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={"X-CS-USERUUID": "undefined"} - url=f"{url}/recon/aggregates/rules-preview/GET/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_a_report_pdf_attachment(self, url, client_id, client_secret, id, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/intel/entities/report-files/v1?id={id}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def retrieve_a_set_of_prevention_policies(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/policy/entities/prevention/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def delete_a_set_of_prevention_policies(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/policy/entities/prevention/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.delete(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def create_prevention_policies(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/policy/entities/prevention/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def update_prevention_policies(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/policy/entities/prevention/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.patch(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_putfiles_based_on_the_ids_given(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/real-time-response/entities/put-files/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def delete_a_putfile_based_on_the_ids_given(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/real-time-response/entities/put-files/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.delete(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def upload_a_new_putfile_to_use_for_the_rtr_put_command(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/real-time-response/entities/put-files/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_a_list_of_session_ids(self, url, client_id, client_secret, headers="", queries="", offset="", limit="", sort="", filter=""): - params={} - request_headers={} - url=f"{url}/real-time-response/queries/sessions/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - if filter: - params["filter"] = filter - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def retrieve_list_of_samples(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={"Content-Type": "application/json","Accept": "application/jsonX-CS-USERUUID"} - url=f"{url}/samples/queries/samples/GET/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def check_status_of_sandbox_analysis(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/falconx/entities/submissions/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def submit_upload_for_sandbox_analysis(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/falconx/entities/submissions/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_number_of_hosts_that_have_observed_a_given_custom_ioc(self, url, client_id, client_secret, type, value, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/indicators/aggregates/devices-count/v1?type={type}&value={value}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def set_precedence_of_firewall_policies(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/policy/entities/firewall-precedence/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_notification_aggregates(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={"Content-Type": "application/json","Accept": "application/json"} - url=f"{url}/recon/aggregates/notifications/GET/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_actions_based_on_their_ids(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/recon/entities/actions/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def delete_an_action_from_a_monitoring_rule_based_on_the_action_id(self, url, client_id, client_secret, id, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/recon/entities/actions/v1?id={id}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.delete(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def create_actions_for_a_monitoring_rule(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={"Content-Type": "application/json","Accept": "application/json"} - url=f"{url}/recon/entities/actions/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def update_an_action_for_a_monitoring_rule(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={"Content-Type": "application/json","Accept": "application/json"} - url=f"{url}/recon/entities/actions/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.patch(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def query_actions(self, url, client_id, client_secret, headers="", queries="", offset="", limit="", sort="", filter="", q=""): - params={} - request_headers={} - url=f"{url}/recon/queries/actions/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - if filter: - params["filter"] = filter - if q: - params["q"] = q - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def search_for_host_group_ids(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): - params={} - request_headers={} - url=f"{url}/devices/queries/host-groups/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def retrieve_indexed_files_metadata_by_their_hash(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/malquery/entities/metadata/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def search_for_sensor_update_policies_with_additional_support_for_uninstall_protection(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): - params={} - request_headers={} - url=f"{url}/policy/combined/sensor-update/v2" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def perform_action_on_the_firewall_policies(self, url, client_id, client_secret, action_name, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/policy/entities/firewall-actions/v1?action_name={action_name}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_process_details(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/processes/entities/processes/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_a_short_summary_version_of_a_sandbox_report(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/falconx/entities/report-summaries/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def schedule_a_yara_based_search_for_execution(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={"Content-Type": "application/json","Accept": "application/json"} - url=f"{url}/malquery/queries/hunt/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def retrieve_the_status_of_batch_get_command__will_return_successful_files_when_they_are_finished_processing(self, url, client_id, client_secret, batch_get_cmd_req_id, headers="", queries="", timeout="", timeout_duration=""): - params={} - request_headers={} - url=f"{url}/real-time-response/combined/batch-get-command/v1?batch_get_cmd_req_id={batch_get_cmd_req_id}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if timeout_duration: - params["timeout_duration"] = timeout_duration - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def batch_executes_get_command_across_hosts_to_retrieve_files_after_this_call_is_made_get_realtimeresponsecombinedbatchgetcommandv1_is_used_to_query_for_the_results(self, url, client_id, client_secret, headers="", queries="", timeout="", timeout_duration="", body=""): - params={} - request_headers={} - url=f"{url}/real-time-response/combined/batch-get-command/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if timeout_duration: - params["timeout_duration"] = timeout_duration - body = " ".join(body.strip().split()).encode("utf-8") - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def query_monitoring_rules(self, url, client_id, client_secret, headers="", queries="", offset="", limit="", sort="", filter="", q=""): - params={} - request_headers={"X-CS-USERUUID": "undefined"} - url=f"{url}/recon/queries/rules/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - if filter: - params["filter"] = filter - if q: - params["q"] = q - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_sensor_installer_details_by_provided_sha256_ids(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/sensors/entities/installers/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def modify_host_tags(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={"Content-Type": "application/json","Accept": "application/json"} - url=f"{url}/devices/entities/devices/tags/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.patch(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def search_for_response_policy_member_ids(self, url, client_id, client_secret, headers="", queries="", id="", filter="", offset="", limit="", sort=""): - params={} - request_headers={} - url=f"{url}/policy/queries/response-members/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if filter: - params["filter"] = filter - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_status_of_an_executed_rtr_administrator_command_on_a_single_host(self, url, client_id, client_secret, cloud_request_id, sequence_id, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/real-time-response/entities/admin-command/v1?cloud_request_id={cloud_request_id}&sequence_id={sequence_id}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def execute_a_rtr_administrator_command_on_a_single_host(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/real-time-response/entities/admin-command/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def refresh_an_active_event_stream(self, url, client_id, client_secret, action_name, appId, partition, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/sensors/entities/datafeed-actions/v1/{partition}?action_name={action_name}&appId={appId}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def validates_field_values_and_checks_for_string_matches(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={"Content-Type": "application/json","Accept": "application/json"} - url=f"{url}/ioarules/entities/rules/validate/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def check_the_status_of_a_volume_scan(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/scanner/entities/scans/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def submit_a_volume_of_files_for_ml_scanning(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/scanner/entities/scans/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def download_the_latest_rule_set(self, url, client_id, client_secret, type, headers="", queries="", format=""): - params={} - request_headers={"Accept": "undefined"} - url=f"{url}/intel/entities/rules-latest-files/v1?type={type}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_rules_by_id(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={"Content-Type": "application/json","Accept": "application/json"} - url=f"{url}/ioarules/entities/rules/GET/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def find_all_rule_groups(self, url, client_id, client_secret, headers="", queries="", sort="", filter="", q="", offset="", limit=""): - params={} - request_headers={} - url=f"{url}/ioarules/queries/rule-groups-full/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if filter: - params["filter"] = filter - if q: - params["q"] = q - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def check_the_status_and_results_of_an_asynchronous_request(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/malquery/entities/requests/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_a_set_of_ml_exclusions(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/policy/entities/ml-exclusions/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def delete_the_ml_exclusions_by_id(self, url, client_id, client_secret, ids, headers="", queries="", comment=""): - params={} - request_headers={} - url=f"{url}/policy/entities/ml-exclusions/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.delete(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def create_the_ml_exclusions(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/policy/entities/ml-exclusions/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def update_the_ml_exclusions(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/policy/entities/ml-exclusions/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.patch(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def search_for_device_control_policy_ids(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): - params={} - request_headers={} - url=f"{url}/policy/queries/device-control/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def search_for_firewall_policy_member_ids(self, url, client_id, client_secret, headers="", queries="", id="", filter="", offset="", limit="", sort=""): - params={} - request_headers={} - url=f"{url}/policy/queries/firewall-members/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if filter: - params["filter"] = filter - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_notifications_based_on_their_ids(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/recon/entities/notifications-translated/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def search_for_host_group_members(self, url, client_id, client_secret, headers="", queries="", id="", filter="", offset="", limit="", sort=""): - params={} - request_headers={} - url=f"{url}/devices/combined/host-group-members/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if filter: - params["filter"] = filter - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_platforms_by_id(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/ioarules/entities/platforms/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def perform_action_on_the_response_policies(self, url, client_id, client_secret, action_name, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/policy/entities/response-actions/v1?action_name={action_name}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def retrieve_a_set_of_response_policies(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/policy/entities/response/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def delete_a_set_of_response_policies(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/policy/entities/response/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.delete(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def create_response_policies(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/policy/entities/response/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def update_response_policies(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/policy/entities/response/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.patch(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def batch_executes_a_rtr_readonly_command(self, url, client_id, client_secret, headers="", queries="", timeout="", timeout_duration="", body=""): - params={} - request_headers={} - url=f"{url}/real-time-response/combined/batch-command/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if timeout_duration: - params["timeout_duration"] = timeout_duration - body = " ".join(body.strip().split()).encode("utf-8") - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_session_metadata_by_session_id(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/real-time-response/entities/sessions/GET/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def perform_action_on_host_group(self, url, client_id, client_secret, action_name, host_group_id, hostnames, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/devices/entities/host-group-actions/v1?action_name={action_name}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - body = {"action_parameters": [{"name": "filter", "value": "(hostname:['" + hostnames + "'])" } ], "ids": [ host_group_id ]} - ret = requests.post(url, headers=request_headers, params=params, json=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def search_for_device_control_policy_members(self, url, client_id, client_secret, headers="", queries="", id="", filter="", offset="", limit="", sort=""): - params={} - request_headers={} - url=f"{url}/policy/combined/device-control-members/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if filter: - params["filter"] = filter - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def search_for_firewall_policies(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): - params={} - request_headers={} - url=f"{url}/policy/combined/firewall/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def retrieve_a_set_of_sensor_update_policies_with_additional_support_for_uninstall_protection(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/policy/entities/sensor-update/v2?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def create_sensor_update_policies(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/policy/entities/sensor-update/v2" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def update_sensor_update_policies(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/policy/entities/sensor-update/v2" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.patch(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_a_list_of_putfile_ids(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): - params={} - request_headers={} - url=f"{url}/real-time-response/queries/put-files/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_a_list_of_custom_script_ids(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): - params={} - request_headers={} - url=f"{url}/real-time-response/queries/scripts/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_detailed_notifications_based_on_their_ids_with_raw_intelligence_content_that_generated_the_match(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/recon/entities/notifications-detailed/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_all_event_streams(self, url, client_id, client_secret, appId, headers="", queries="", format=""): - params={} - request_headers={} - url=f"{url}/sensors/entities/datafeed/v2?appId={appId}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def download_sensor_installer_by_sha256_id(self, url, client_id, client_secret, id, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/sensors/entities/download-installer/v1?id={id}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_hosts_that_have_observed_a_given_custom_ioc(self, url, client_id, client_secret, type, value, headers="", queries="", limit="", offset=""): - params={} - request_headers={} - url=f"{url}/indicators/queries/devices/v1?type={type}&value={value}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if offset: - params["offset"] = offset - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def retrieve_details_for_rule_sets_for_ids(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/intel/entities/rules/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def download_a_file_indexed_by_malquery(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/malquery/entities/download-files/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def retrieve_an_uninstall_token_for_a_specific_device(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/policy/combined/reveal-uninstall-token/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def search_for_response_policy_ids(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): - params={} - request_headers={} - url=f"{url}/policy/queries/response/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_a_list_of_files_for_rtr_session(self, url, client_id, client_secret, session_id, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/real-time-response/entities/file/v1?session_id={session_id}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def delete_a_rtr_session_file(self, url, client_id, client_secret, ids, session_id, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/real-time-response/entities/file/v1?ids={ids}&session_id={session_id}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.delete(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_custom_scripts_based_on_the_ids_given(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/real-time-response/entities/scripts/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def delete_a_custom_script_based_on_the_id_given(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/real-time-response/entities/scripts/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.delete(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def upload_a_new_custom_script_to_use(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/real-time-response/entities/scripts/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def upload_a_new_scripts_to_replace_an_existing_one(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/real-time-response/entities/scripts/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.patch(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_details_on_hosts(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/devices/entities/devices/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_actor_ids(self, url, client_id, client_secret, headers="", queries="", offset="", limit="", sort="", filter="", q=""): - params={} - request_headers={} - url=f"{url}/intel/queries/actors/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - if filter: - params["filter"] = filter - if q: - params["q"] = q - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_ccid_to_use_with_sensor_installers(self, url, client_id, client_secret, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/sensors/queries/installers/ccid/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def find_submission_ids_for_uploaded_files(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): - params={} - request_headers={} - url=f"{url}/falconx/queries/submissions/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_details_on_behaviors(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={"Content-Type": "application/json","Accept": "application/json"} - url=f"{url}/incidents/entities/behaviors/GET/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def search_for_device_control_policies(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): - params={} - request_headers={} - url=f"{url}/policy/combined/device-control/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def search_for_prevention_policy_member_ids(self, url, client_id, client_secret, headers="", queries="", id="", filter="", offset="", limit="", sort=""): - params={} - request_headers={} - url=f"{url}/policy/queries/prevention-members/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if filter: - params["filter"] = filter - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_status_of_an_executed_command_on_a_single_host(self, url, client_id, client_secret, cloud_request_id, sequence_id, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/real-time-response/entities/command/v1?cloud_request_id={cloud_request_id}&sequence_id={sequence_id}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def execute_a_command_on_a_single_host(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/real-time-response/entities/command/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def retrieve_the_file_associated_with_the_given_id_sha256(self, url, client_id, client_secret, ids, headers="", queries="", password_protected=""): - params={} - request_headers={"X-CS-USERUUID": "undefined"} - url=f"{url}/samples/entities/samples/v3?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def delete_sample_from_the_collection(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={"X-CS-USERUUID": "undefined"} - url=f"{url}/samples/entities/samples/v3?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.delete(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def upload_a_file_for_further_cloud_analysis(self, url, client_id, client_secret, file_name, headers="", queries="", comment="", is_confidential="", body=""): - params={} - request_headers={"X-CS-USERUUID": "undefined"} - url=f"{url}/samples/entities/samples/v3?file_name={file_name}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if is_confidential: - params["is_confidential"] = is_confidential - body = " ".join(body.strip().split()).encode("utf-8") - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def search_for_response_policies(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): - params={} - request_headers={} - url=f"{url}/policy/combined/response/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def retrieve_a_set_of_firewall_policies(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/policy/entities/firewall/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def delete_a_set_of_firewall_policies(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/policy/entities/firewall/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.delete(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def create_firewall_policies(self, url, client_id, client_secret, headers="", queries="", clone_id="", body=""): - params={} - request_headers={} - url=f"{url}/policy/entities/firewall/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - body = " ".join(body.strip().split()).encode("utf-8") - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def update_firewall_policies(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/policy/entities/firewall/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.patch(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def set_precedence_of_sensor_update_policies(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/policy/entities/sensor-update-precedence/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def search_for_device_control_policy_member_ids(self, url, client_id, client_secret, headers="", queries="", id="", filter="", offset="", limit="", sort=""): - params={} - request_headers={} - url=f"{url}/policy/queries/device-control-members/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if filter: - params["filter"] = filter - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def batch_executes_a_rtr_active_responder_command(self, url, client_id, client_secret, headers="", queries="", timeout="", timeout_duration="", body=""): - params={} - request_headers={} - url=f"{url}/real-time-response/combined/batch-active-responder-command/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if timeout_duration: - params["timeout_duration"] = timeout_duration - body = " ".join(body.strip().split()).encode("utf-8") - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def batch_executes_a_rtr_administrator_command(self, url, client_id, client_secret, headers="", queries="", timeout="", timeout_duration="", body=""): - params={} - request_headers={} - url=f"{url}/real-time-response/combined/batch-admin-command/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if timeout_duration: - params["timeout_duration"] = timeout_duration - body = " ".join(body.strip().split()).encode("utf-8") - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_monitoring_rules_rules_by_provided_ids(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={"X-CS-USERUUID": "undefined"} - url=f"{url}/recon/entities/rules/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def delete_monitoring_rules(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={"X-CS-USERUUID": "undefined"} - url=f"{url}/recon/entities/rules/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.delete(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def create_monitoring_rules(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={"X-CS-USERUUID": "undefined"} - url=f"{url}/recon/entities/rules/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def update_monitoring_rules(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={"X-CS-USERUUID": "undefined"} - url=f"{url}/recon/entities/rules/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.patch(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def search_for_detection_ids(self, url, client_id, client_secret, headers="", queries="", offset="", limit="", sort="", filter="", q=""): - params={} - request_headers={} - url=f"{url}/detects/queries/detects/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - if filter: - params["filter"] = filter - if q: - params["q"] = q - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def retrieve_the_file_associated_with_the_given_id_sha256(self, url, client_id, client_secret, ids, headers="", queries="", password_protected=""): - params={} - request_headers={"X-CS-USERUUID": "undefined"} - url=f"{url}/samples/entities/samples/v2?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def upload_for_sandbox_analysis(self, url, client_id, client_secret, file_name, headers="", queries="", comment="", is_confidential="", body=""): - params={} - request_headers={"X-CS-USERUUID": "undefined"} - url=f"{url}/samples/entities/samples/v2?file_name={file_name}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if is_confidential: - params["is_confidential"] = is_confidential - body = " ".join(body.strip().split()).encode("utf-8") - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def search_for_host_group_member_ids(self, url, client_id, client_secret, headers="", queries="", id="", filter="", offset="", limit="", sort=""): - params={} - request_headers={} - url=f"{url}/devices/queries/host-group-members/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if filter: - params["filter"] = filter - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_details_on_incidents(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={"Content-Type": "application/json","Accept": "application/json"} - url=f"{url}/incidents/entities/incidents/GET/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def search_for_processes_associated_with_a_custom_ioc(self, url, client_id, client_secret, type, value, device_id, headers="", queries="", limit="", offset=""): - params={} - request_headers={} - url=f"{url}/indicators/queries/processes/v1?type={type}&value={value}&device_id={device_id}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if offset: - params["offset"] = offset - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def retrieve_specific_reports_using_their_report_ids(self, url, client_id, client_secret, ids, headers="", queries="", fields=""): - params={} - request_headers={} - url=f"{url}/intel/entities/reports/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def search_for_indicators(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): - params={} - request_headers={} - url=f"{url}/iocs/queries/indicators/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def search_for_firewall_policy_members(self, url, client_id, client_secret, headers="", queries="", id="", filter="", offset="", limit="", sort=""): - params={} - request_headers={} - url=f"{url}/policy/combined/firewall-members/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if filter: - params["filter"] = filter - if offset: - params["offset"] = offset - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def perform_action_on_the_prevention_policies(self, url, client_id, client_secret, action_name, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/policy/entities/prevention-actions/v1?action_name={action_name}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def retrieve_a_set_of_sensor_update_policies(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/policy/entities/sensor-update/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def delete_a_set_of_sensor_update_policies(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/policy/entities/sensor-update/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.delete(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def create_sensor_update_policies(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/policy/entities/sensor-update/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def update_sensor_update_policies(self, url, client_id, client_secret, headers="", queries="", body=""): - params={} - request_headers={} - url=f"{url}/policy/entities/sensor-update/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.patch(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def take_action_on_hosts(self, url, client_id, client_secret, action_name, headers="", queries="", body=""): - params={} - request_headers={"Content-Type": "application/json","Accept": "application/json"} - url=f"{url}/devices/entities/devices-actions/v2?action_name={action_name}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.post(url, headers=request_headers, params=params, data=body) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def search_for_hosts(self, url, client_id, client_secret, headers="", queries="", offset="", limit="", sort="", filter=""): - params={} - request_headers={} - url=f"{url}/devices/queries/devices/v1" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - if limit: - params["limit"] = limit - if sort: - params["sort"] = sort - if filter: - params["filter"] = filter - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def retrieve_specific_actors_using_their_actor_ids(self, url, client_id, client_secret, ids, headers="", queries="", fields=""): - params={} - request_headers={} - url=f"{url}/intel/entities/actors/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - - def get_pattern_severities_by_id(self, url, client_id, client_secret, ids, headers="", queries=""): - params={} - request_headers={} - url=f"{url}/ioarules/entities/pattern-severities/v1?ids={ids}" - request_headers=self.setup_headers(headers) - params=self.setup_params(queries) - - ret = requests.get(url, headers=request_headers, params=params) - try: - return ret.json() - except json.decoder.JSONDecodeError: - return ret.text - - -if __name__ == "__main__": - - Crowdstrike_Falcon.run() From 480daecef372efc6df63487e7dcd81f6bc3f1816 Mon Sep 17 00:00:00 2001 From: Frikky Date: Tue, 30 Jul 2024 12:08:50 +0200 Subject: [PATCH 130/259] Splunk -> Rest API --- splunk/1.0.0/Dockerfile | 26 ------- splunk/1.0.0/api.yaml | 62 ---------------- splunk/1.0.0/docker-compose.yml | 14 ---- splunk/1.0.0/env.txt | 4 -- splunk/1.0.0/requirements.txt | 2 - splunk/1.0.0/src/app.py | 124 -------------------------------- 6 files changed, 232 deletions(-) delete mode 100644 splunk/1.0.0/Dockerfile delete mode 100644 splunk/1.0.0/api.yaml delete mode 100644 splunk/1.0.0/docker-compose.yml delete mode 100644 splunk/1.0.0/env.txt delete mode 100644 splunk/1.0.0/requirements.txt delete mode 100644 splunk/1.0.0/src/app.py diff --git a/splunk/1.0.0/Dockerfile b/splunk/1.0.0/Dockerfile deleted file mode 100644 index bfa83edc..00000000 --- a/splunk/1.0.0/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -# Base our app image off of the WALKOFF App SDK image -FROM frikky/shuffle:app_sdk as base - -# We're going to stage away all of the bloat from the build tools so lets create a builder stage -FROM base as builder - -# Install all alpine build tools needed for our pip installs -RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev - -# Install all of our pip packages in a single directory that we can copy to our base image later -RUN mkdir /install -WORKDIR /install -COPY requirements.txt /requirements.txt -RUN pip install --prefix="/install" -r /requirements.txt - -# Switch back to our base image and copy in all of our built packages and source code -FROM base -COPY --from=builder /install /usr/local -COPY src /app - -# Install any binary dependencies needed in our final image -RUN apk --no-cache add --update libmagic - -# Finally, lets run our app! -WORKDIR /app -CMD python app.py --log-level DEBUG diff --git a/splunk/1.0.0/api.yaml b/splunk/1.0.0/api.yaml deleted file mode 100644 index f63c9dac..00000000 --- a/splunk/1.0.0/api.yaml +++ /dev/null @@ -1,62 +0,0 @@ -walkoff_version: 1.0.0 -app_version: 1.0.0 -name: splunk -description: Splunk integration with WALKOFF -tags: - - SIEM - - search -categories: - - SIEM -contact_info: - name: "@frikkylikeme" - url: https://github.com/frikky -authentication: - required: true - parameters: - - name: url - description: The Splunk URL - required: true - example: "http://splunk:8081" - schema: - type: string - - name: username - description: The Splunk username - example: username@splunk.com - required: true - schema: - type: string - - name: password - description: The Splunk password - required: true - example: "******" - schema: - type: string - -actions: - - name: SplunkQuery - description: Returns the amount of search results - parameters: - - name: query - description: The Splunk query to run - required: true - schema: - type: string - - name: result_limit - description: Splunk amount limit - required: false - schema: - type: string - - name: earliest_time - description: The timeframe to use (e.g. -48h) - required: false - schema: - type: string - - name: latest_time - description: The timeframe to use (e.g. -48h) - required: false - schema: - type: string - returns: - schema: - type: string -large_image: data:image/jpg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAcFBgYGBQcGBgYICAcJCxIMCwoKCxcQEQ0SGxccHBoXGhkdISokHR8oIBkaJTIlKCwtLzAvHSM0ODQuNyouLy7/2wBDAQgICAsKCxYMDBYuHhoeLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi7/wAARCABkAGQDAREAAhEBAxEB/8QAGQABAAMBAQAAAAAAAAAAAAAAAAIEBQYD/8QALRAAAQQCAQMBCAEFAAAAAAAAAAECAwQFERIGITFBExQiMlFhcYGxFRYjQkP/xAAaAQEAAwEBAQAAAAAAAAAAAAAAAQIDBAUG/8QAIxEBAAICAgICAgMAAAAAAAAAAAECAxESIQQxQVETIhQj8P/aAAwDAQACEQMRAD8A0T5l5IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABJsb3Nc5rHOa35lRqqifn6E6k0iQAACT45I9c43N2m05NVNoTMTHs0iQAAAAAkxrnvaxqbc5UaifVVJiNzobWUwUeNclebJwuu/ByrpG7tyXXZ3hTfLgjH1Nu/prfFx632tWOl2VL7aNzM1IZpNeyRWOVXb+v0Tfbv5L28Xjbja0bWnDqdTK/i6NjGYjqqnaaiSxwt8L2VOLtKhrjpOPHkrZelZrW8S5/M4eXFy1I5J2SLZjSRqtRU4oq613OXLhnHMRM+2F8c1137aX9o2lyVnHtuQ84IGzK5WqiKiqqa+3jybfw7c5pv1G2n4J5TXarP0+n9JsZGnk61xtdP8zI0VOH4VfJSfH/Sb1tE6VnF+s2id6b3UuNsZS7ha0GkRKSOkkd8sbe21U6PIxTktSI+muSs2msR9OXoY5bdi3UrzVppWJqJXScEkXf8Apvyv2U5KYuVprGtsK03MxClarWKkyw2YXwyp5a9ulM7Vms6tGlZiYnUvEqgAAThbzlYzm2Pk5E5uXSN7+VUmI3JDuLz2yYVrc3YpzZCKZjak0EqPfI3kndden5/k9G87x/2zEzE9Oq3df39/CfVGK996oSdt2rHGxI/bJLKjHRInfel87T6E+Ri5Zt7j42nLTlfe07mYpX4uqHxTxo11dkUPJyIsukd3RF891JvmpeMmp+EzkrbkrZ2GtmIsTkIMjTjghhayb2kunM0qKvw+VX7FM1YyxW0WjUQreIvxmJati9SXPZiRLcHB+NaxrvaJpzvi7J38m1slPyXnfw0m0crd/Dl+nZ4I+muoYpZo2SSQIjGuciK5dL4T1OLBMRhyRtz45iKWdHdy+Pe+lirM8bqVyk2KWSN6bif21tU8J+TrvmpMxjmephtN69Vn1MOLZj6TMhZgt5SJkEH/AFiTmsvfsjUT1/g4Ix1i0xa3Uf7pzRWNzEysZnNR3KUOOrQye7Qu5Nlsv5yu/fon2L5c8WrFKx1H37Wvk3HGGGczIAAAJwuSOZkmvlcjl166XZMdTtMe2l1Jk48xlZLzIXRNcxreLlRVTSGvkZYy35RC+W/O22UYsz9IA/QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/2Q== diff --git a/splunk/1.0.0/docker-compose.yml b/splunk/1.0.0/docker-compose.yml deleted file mode 100644 index ad612c5d..00000000 --- a/splunk/1.0.0/docker-compose.yml +++ /dev/null @@ -1,14 +0,0 @@ -version: '3.4' -services: - splunk: - build: - context: . - dockerfile: Dockerfile - env_file: - - env.txt - restart: "no" - deploy: - mode: replicated - replicas: 10 - restart_policy: - condition: none diff --git a/splunk/1.0.0/env.txt b/splunk/1.0.0/env.txt deleted file mode 100644 index b5568707..00000000 --- a/splunk/1.0.0/env.txt +++ /dev/null @@ -1,4 +0,0 @@ -REDIS_URI=redis://redis -REDIS_ACTION_RESULT_CH=action-results -REDIS_ACTION_RESULTS_GROUP=action-results-group -APP_NAME=splunk diff --git a/splunk/1.0.0/requirements.txt b/splunk/1.0.0/requirements.txt deleted file mode 100644 index c5a5f6ea..00000000 --- a/splunk/1.0.0/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -python-magic==0.4.18 -requests==2.25.1 \ No newline at end of file diff --git a/splunk/1.0.0/src/app.py b/splunk/1.0.0/src/app.py deleted file mode 100644 index a9a10be4..00000000 --- a/splunk/1.0.0/src/app.py +++ /dev/null @@ -1,124 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import asyncio -import time -import random -import requests -import urllib3 -import json - -from walkoff_app_sdk.app_base import AppBase - -class Splunk(AppBase): - """ - Splunk integration for WALKOFF with some basic features - """ - __version__ = "1.0.0" - app_name = "splunk" - - def __init__(self, redis, logger, console_logger=None): - """ - Each app should have this __init__ to set up Redis and logging. - :param redis: - :param logger: - :param console_logger: - """ - self.verify = False - self.timeout = 10 - urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) - super().__init__(redis, logger, console_logger) - - def echo(self, input_data): - return input_data - - def run_search(self, auth, url, query): - url = '%s/services/search/jobs?output_mode=json' % (url) - ret = requests.post(url, auth=auth, data=query, timeout=self.timeout, verify=False) - return ret - - def get_search(self, auth, url, search_sid): - # Wait for search to be done? - firsturl = '%s/services/search/jobs/%s?output_mode=json' % (url, search_sid) - print("STARTED FUNCTION WITH URL %s" % firsturl) - time.sleep(0.2) - maxrunduration = 30 - ret = "No results yet" - while(True): - try: - ret = requests.get(firsturl, auth=auth, timeout=self.timeout, verify=False) - except requests.exceptions.ConnectionError: - print("Sleeping for 1 second") - time.sleep(1) - continue - - try: - content = ret.json()["entry"][0]["content"] - except KeyError as e: - print("\nKEYERROR: %s\n" % content) - time.sleep(1) - continue - - try: - if content["resultCount"] > 0 or content["isDone"] or content["isFinalized"] or content["runDuration"] > maxrunduration: - print("CONTENT PRE EVENTS: ", content) - eventsurl = '%s/services/search/jobs/%s/events' % (url, search_sid) - print("Running events check towards %s" % eventsurl) - try: - newret = requests.get(eventsurl, auth=auth, timeout=self.timeout, verify=False) - if ret.status_code < 300 and ret.status_code >= 200: - return newret.text - else: - return "Bad status code for events: %sd", ret.status_code - except requests.exceptions.ConnectionError: - return "Events requesterror: %s" % e - except KeyError: - try: - return ret.json()["messages"] - except KeyError as e: - return "KeyError: %s" % e - - time.sleep(1) - - return ret - - def SplunkQuery(self, url, username, password, query, result_limit=100, earliest_time="-24h", latest_time="now"): - auth = (username, password) - - # "latest_time": "now" - query = { - "search": "| search %s" % query, - "exec_mode": "normal", - "count": result_limit, - "earliest_time": earliest_time, - "latest_time": latest_time - } - - print("Current search: %s" % query["search"]) - - try: - ret = self.run_search(auth, url, query) - except requests.exceptions.ConnectTimeout as e: - print("Timeout: %s" % e) - return "Timeout: %s" % e - - if ret.status_code != 201: - print("Bad status code: %d" % ret.status_code) - return "Bad status code: %d" % ret.status_code - - search_id = ret.json()["sid"] - - print("Search ID: %s" % search_id) - - ret = self.get_search(auth, url, search_id) - return ret - #if len(ret.json()["entry"]) == 1: - # count = ret.json()["entry"][0]["content"]["resultCount"] - # print("Result: %d" % count) - # return str(count) - - #print("No results (or wrong?): %d" % (len(ret.json()["entry"]))) - #return "No results" - -if __name__ == "__main__": - Splunk.run() From 3f3cc6d323e1436a438bc592c6c33f2278666d8f Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 7 Aug 2024 12:07:37 +0200 Subject: [PATCH 131/259] removed testing app --- shuffle-tools/1.2.0/api.yaml | 38 +++---- testing/1.0.0/Dockerfile | 26 ----- testing/1.0.0/api.yaml | 178 --------------------------------- testing/1.0.0/requirements.txt | 1 - testing/1.0.0/run | 17 ---- testing/1.0.0/src/app.py | 101 ------------------- testing/1.0.0/tmp.py | 128 ------------------------ 7 files changed, 19 insertions(+), 470 deletions(-) delete mode 100644 testing/1.0.0/Dockerfile delete mode 100644 testing/1.0.0/api.yaml delete mode 100644 testing/1.0.0/requirements.txt delete mode 100755 testing/1.0.0/run delete mode 100644 testing/1.0.0/src/app.py delete mode 100644 testing/1.0.0/tmp.py diff --git a/shuffle-tools/1.2.0/api.yaml b/shuffle-tools/1.2.0/api.yaml index 14f4f258..d075ddc9 100644 --- a/shuffle-tools/1.2.0/api.yaml +++ b/shuffle-tools/1.2.0/api.yaml @@ -1106,25 +1106,25 @@ actions: returns: schema: type: string - - name: get_standardized_data - description: 'Used to run standardized synonym translations of data to make it easier to use in automation. This can be done automatically in subflows as well' - parameters: - - name: json_input - description: The full JSON blob to automatically translate - required: true - multiline: true - example: '{"ref": "1234"}' - schema: - type: string - - name: input_type - description: The data type of the input - required: true - multiline: true - example: 'cases' - options: - - cases - schema: - type: string + #- name: get_standardized_data + # description: 'Used to run standardized synonym translations of data to make it easier to use in automation. This can be done automatically in subflows as well' + # parameters: + # - name: json_input + # description: The full JSON blob to automatically translate + # required: true + # multiline: true + # example: '{"ref": "1234"}' + # schema: + # type: string + # - name: input_type + # description: The data type of the input + # required: true + # multiline: true + # example: 'cases' + # options: + # - cases + # schema: + # type: string - name: generate_random_string description: 'Used to generate passwords and random strings' parameters: diff --git a/testing/1.0.0/Dockerfile b/testing/1.0.0/Dockerfile deleted file mode 100644 index 364e1531..00000000 --- a/testing/1.0.0/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -# Base our app image off of the WALKOFF App SDK image -FROM frikky/shuffle:app_sdk as base - -# We're going to stage away all of the bloat from the build tools so lets create a builder stage -FROM base as builder - -# Install all alpine build tools needed for our pip installs -RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev - -# Install all of our pip packages in a single directory that we can copy to our base image later -RUN mkdir /install -WORKDIR /install -COPY requirements.txt /requirements.txt -RUN pip install --prefix="/install" -r /requirements.txt - -# Switch back to our base image and copy in all of our built packages and source code -FROM base -COPY --from=builder /install /usr/local -COPY src /app - -# Install any binary dependencies needed in our final image -# RUN apk --no-cache add --update my_binary_dependency - -# Finally, lets run our app! -WORKDIR /app -CMD python app.py --log-level DEBUG diff --git a/testing/1.0.0/api.yaml b/testing/1.0.0/api.yaml deleted file mode 100644 index e1ee1c6c..00000000 --- a/testing/1.0.0/api.yaml +++ /dev/null @@ -1,178 +0,0 @@ -app_version: 1.0.0 -name: Testing -description: Debugging app for Shuffle -tags: - - Testing -categories: - - Testing -contact_info: - name: "@frikkylikeme" - url: https://shuffler.io - email: frikky@shuffler.io -actions: - - name: hello_world - description: Returns Hello World from the hostname the action is run on - returns: - example: HELLO WORLD FROM host.name - returns: - schema: - type: string - - name: repeat_back_to_me - description: Repeats the call parameter - parameters: - - name: call - description: The message to repeat - required: true - multiline: true - example: "REPEATING: Hello world" - schema: - type: string - returns: - schema: - type: string - - name: repeat_back_to_me_multi - description: Repeats the call parameter - parameters: - - name: call - description: The message to repeat - required: true - multiline: true - example: "REPEATING: Hello world" - schema: - type: string - - name: call2 - description: The message to repeat - required: true - multiline: true - example: "REPEATING: Hello world" - schema: - type: string - - name: call3 - description: The message to repeat - required: true - multiline: true - example: "REPEATING: Hello world" - schema: - type: string - returns: - schema: - type: string - - name: return_plus_one - description: Increments the number parameter by 1 - parameters: - - name: number - description: number to increment - required: true - schema: - type: number - example: number(2) - returns: - schema: - type: number - - name: get_type - description: Get the type of a variable - parameters: - - name: value - description: The value to check - required: true - example: '{"return": number(0)}' - schema: - type: string - returns: - schema: - type: number - - name: pause - description: Pause execution by the seconds parameter - parameters: - - name: seconds - description: seconds to pause for - required: true - example: number(3) - schema: - type: number - returns: - schema: - type: number - - name: raise_error - description: This function doesn't exist and is here to test errors - returns: - schema: - type: string - - name: input_options_test - description: Input testing Shuffle - parameters: - - name: call - description: The message to repeat - options: - - hey - - how - - are - - you - required: true - multiline: true - example: "REPEATING: Hello world" - schema: - type: string - returns: - schema: - type: string - - name: get_file_value - description: This function is made for reading file(s), printing their data - parameters: - - name: filedata - description: The files - required: true - multiline: true - example: "REPEATING: Hello world" - schema: - type: file - returns: - schema: - type: string - - name: create_file - description: Returns uploaded file data - parameters: - - name: filename - description: - required: true - multiline: false - example: "test.txt" - schema: - type: string - - name: data - description: - required: true - multiline: true - example: "Some data to put in the file" - schema: - type: string - returns: - schema: - type: file - - name: download_file - description: Downloads a file from a URL - parameters: - - name: url - description: - required: true - multiline: false - example: "https://secure.eicar.org/eicar.com.txt" - schema: - type: string - returns: - schema: - type: string - - name: delete_file - description: Deletes a file based on ID - parameters: - - name: file_id - description: - required: true - multiline: false - example: "Some data to put in the file" - schema: - type: string - returns: - schema: - type: string -large_image: data:image/png;base64,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 diff --git a/testing/1.0.0/requirements.txt b/testing/1.0.0/requirements.txt deleted file mode 100644 index fd7d3e06..00000000 --- a/testing/1.0.0/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -requests==2.25.1 \ No newline at end of file diff --git a/testing/1.0.0/run b/testing/1.0.0/run deleted file mode 100755 index e73f748d..00000000 --- a/testing/1.0.0/run +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/sh -docker stop frikky/shuffle:testing_1.0.0 --force -docker rm frikky/shuffle:testing_1.0.0 --force -docker rmi frikky/shuffle:testing_1.0.0 --force - -docker build . -t frikky/shuffle:testing_1.0.0 - -echo "RUNNING!\n\n" -docker run \ - --env CALLBACK_URL="http://192.168.239.144:5001" \ - --env ACTION='{"app_name":"testing","app_version":"1.0.0","errors":[],"id_":"13fa4c3f-8991-3ade-b90d-f326fd4941dd","is_valid":true,"label":"random_number","environment":"onprem","name":"random_number","parameters":[],"position":{"x":178.07868996109607,"y":457.28345902971614},"priority":3}' \ - --env FUNCTION_APIKEY="asdasd" \ - --env EXECUTIONID="2349bf96-51ad-68d2-5ca6-75ef8f7ee814" \ - --env AUTHORIZATION="8e344a2e-db51-448f-804c-eb959a32c139" \ - frikky/shuffle:testing_1.0.0 - -docker push frikky/shuffle:testing_1.0.0 diff --git a/testing/1.0.0/src/app.py b/testing/1.0.0/src/app.py deleted file mode 100644 index de090ef2..00000000 --- a/testing/1.0.0/src/app.py +++ /dev/null @@ -1,101 +0,0 @@ -import socket -import asyncio -import time -import random -import json -import requests - -from walkoff_app_sdk.app_base import AppBase - -class HelloWorld(AppBase): - """ - An example of a Walkoff App. - Inherit from the AppBase class to have Redis, logging, and console logging set up behind the scenes. - """ - __version__ = "1.0.0" - app_name = "hello_world" # this needs to match "name" in api.yaml - - def __init__(self, redis, logger, console_logger=None): - """ - Each app should have this __init__ to set up Redis and logging. - :param redis: - :param logger: - :param console_logger: - """ - super().__init__(redis, logger, console_logger) - - def hello_world(self): - """ - Returns Hello World from the hostname the action is run on - :return: Hello World from your hostname - """ - message = f"Hello World from {socket.gethostname()} in workflow {self.current_execution_id}!" - - # This logs to the docker logs - self.logger.info(message) - - return message - - def repeat_back_to_me(self, call): - return call - - def repeat_back_to_me_multi(self, call, call2, call3): - return {"call1": call, "call2": call2, "call3": call3} - - def return_plus_one(self, number): - return int(number) + 1 - - def pause(self, seconds): - time.sleep(seconds) - return "Waited %d seconds" % seconds - - def get_type(self, value): - return "Type: %s" % type(value) - - def input_options_test(self, call): - return "Value: %s" % call - - def get_file_value(self, filedata): - if filedata == None: - return "File is empty?" - - print("INSIDE APP DATA: %s" % filedata) - return "%s" % filedata["data"].decode() - - def create_file(self, filename, data): - print("Inside function") - filedata = { - "filename": filename, - "data": data, - } - - fileret = self.set_files([filedata]) - value = {"success": True, "file_ids": fileret} - return value - #print("Done with upload function") - - #return ("Successfully put your data in a file", filedata) - - def download_file(self, url): - ret = requests.get(url, verify=False) - fileret = self.set_files([{ - "filename": "downloaded", - "data": ret.content, - }]) - - value = {"success": True, "file_ids": fileret} - return value - - #return ("Successfully put your data in a file", filedata) - - def delete_file(self, file_id): - headers = { - "Authorization": "Bearer %s" % self.authorization, - } - print("HEADERS: %s" % headers) - - ret = requests.delete("%s/api/v1/files/%s?execution_id=%s" % (self.base_url, file_id, self.current_execution_id), headers=headers) - return ret.text - -if __name__ == "__main__": - HelloWorld.run() diff --git a/testing/1.0.0/tmp.py b/testing/1.0.0/tmp.py deleted file mode 100644 index 2c3698ea..00000000 --- a/testing/1.0.0/tmp.py +++ /dev/null @@ -1,128 +0,0 @@ -import json -import re - -# This whole thing should be recursive. -basejson = [{'highlight_ranges': {}, 'message': {'Alert': 'Account Manipulation', '_id': 'd097c6f2-f6b6-11ea-aaa1-0050569f425d', 'timestamp': '2020-09-14T18:19:24.427Z'}, 'index': 'test', 'decoration_stats': None}, {'highlight_ranges': {}, 'message': {'Alert': 'Account Manipulation', '_id': 'd099c2c3-f6b6-11ea-aaa1-0050569f425d', 'timestamp': '2020-09-14T18:19:24.427Z'}, 'index': 'test', 'decoration_stats': None}, {'highlight_ranges': {}, 'message': {'Alert': 'Account Manipulation', '_id': 'd097c6f2-f6b6-11ea-aaa1-0050569f425d', 'timestamp': '2020-09-14T18:19:24.427Z'}, 'index': 'mitre_0', 'decoration_stats': None}, {'highlight_ranges': {}, 'message': {'Alert': 'Account Manipulation', '_id': 'd099c2c3-f6b6-11ea-aaa1-0050569f425d', 'timestamp': '2020-09-14T18:19:24.427Z'}, 'index': 'mitre_0', 'decoration_stats': None}, {'highlight_ranges': {}, 'message': {'Alert': 'Notepad connecting to the internet', '_id': 'c789d084-f6b6-11ea-aaa1-0050569f425d', 'timestamp': '2020-09-14T18:19:09.444Z'}, 'index': '1_207', 'decoration_stats': None}, {'highlight_ranges': {}, 'message': {'Alert': 'Notepad connecting to the internet', '_id': 'c789d084-f6b6-11ea-aaa1-0050569f425d', 'timestamp': '2020-09-14T18:19:09.444Z'}, 'index': 'mitre_0', 'decoration_stats': None}, {'highlight_ranges': {}, 'message': {'Alert': 'Obfuscating Hacking Commands', '_id': 'ae8ad8f5-f6b5-11ea-aaa1-0050569f425d', 'timestamp': '2020-09-14T18:11:17.202Z'}, 'index': 'mitre_0', 'decoration_stats': None}, {'highlight_ranges': {}, 'message': {'Alert': 'Account Manipulation', '_id': '0f9d3001-f6b3-11ea-aaa1-0050569f425d', 'timestamp': '2020-09-14T17:52:31.810Z'}, 'index': 'test_201', 'decoration_stats': None}, {'highlight_ranges': {}, 'message': {'Alert': 'Account Manipulation', '_id': '0f9d3000-f6b3-11ea-aaa1-0050569f425d', 'timestamp': '2020-09-14T17:52:31.810Z'}, 'index': 'test_201', 'decoration_stats': None}, {'highlight_ranges': {}, 'message': {'Alert': 'Account Manipulation', '_id': '0f9d3001-f6b3-11ea-aaa1-0050569f425d', 'timestamp': '2020-09-14T17:52:31.810Z'}, 'index': 'mitre_0', 'decoration_stats': None}, {'highlight_ranges': {}, 'message': {'Alert': 'Account Manipulation', '_id': '0f9d3000-f6b3-11ea-aaa1-0050569f425d', 'timestamp': '2020-09-14T17:52:31.810Z'}, 'index': 'mitre_0', 'decoration_stats': None}] -#basejson = json.loads(baseresult) - -#ACTUAL: [('$Start_node.#.message', 'Start_node.', 'message')] -input_data = "$Start_node.#4:max.message.Alert" - - -def recurse_loop(basejson, parsersplit): - #parsersplit = input_data.split(".") - - match = "#(\d+):?-?([0-9a-z]+)?#?" - print("Split: %s\n%s" % (parsersplit, basejson)) - try: - outercnt = 0 - for value in parsersplit: - print("VALUE: %s\n" % value) - actualitem = re.findall(match, value, re.MULTILINE) - if value == "#": - newvalue = [] - for innervalue in basejson: - # 1. Check the next item (message) - # 2. Call this function again - - try: - ret = recurse_loop(innervalue, parsersplit[outercnt+1:]) - except IndexError: - print("INDEXERROR: ", parsersplit[outercnt]) - #ret = innervalue - ret = recurse_loop(innervalue, parsersplit[outercnt:]) - - print(ret) - #exit() - newvalue.append(ret) - - return newvalue - elif len(actualitem) > 0: - # FIXME: This is absolutely not perfect. - print("IN HERE: ", actualitem) - - newvalue = [] - firstitem = actualitem[0][0] - seconditem = actualitem[0][1] - if seconditem == "": - print("In first") - basejson = basejson[int(firstitem)] - else: - if seconditem == "max": - seconditem = len(basejson) - if seconditem == "min": - seconditem = 0 - - newvalue = [] - for i in range(int(firstitem), int(seconditem)): - # 1. Check the next item (message) - # 2. Call this function again - print("Base: %s" % basejson[i]) - - try: - ret = recurse_loop(basejson[i], parsersplit[outercnt+1:]) - except IndexError: - print("INDEXERROR: ", parsersplit[outercnt]) - #ret = innervalue - ret = recurse_loop(innervalue, parsersplit[outercnt:]) - - print(ret) - #exit() - newvalue.append(ret) - - return newvalue - else: - #print("BEFORE NORMAL VALUE: ", basejson, value) - if len(value) == 0: - return basejson - - if isinstance(basejson[value], str): - print(f"LOADING STRING '%s' AS JSON" % basejson[value]) - try: - basejson = json.loads(basejson[value]) - except json.decoder.JSONDecodeError as e: - print("RETURNING BECAUSE '%s' IS A NORMAL STRING" % basejson[value]) - return basejson[value] - else: - basejson = basejson[value] - - outercnt += 1 - - except KeyError as e: - print("Lower keyerror: %s" % e) - #return basejson - #return "KeyError: Couldn't find key: %s" % e - - return basejson - -ret = recurse_loop(basejson, input_data.split(".")[1:]) -print(ret) - - - - # FIXME - not recursive - should go deeper if there are more # - #print("HANDLE RECURSIVE LOOP OF %s" % basejson) - #returnlist = [] - #try: - # for innervalue in basejson: - # print("Value: %s" % innervalue[parsersplit[cnt+1]]) - # returnlist.append(innervalue[parsersplit[cnt+1]]) - #except IndexError as e: - # print("Indexerror inner: %s" % e) - # # Basically means its a normal list, not a crazy one :) - # # Custom format for ${name[0,1,2,...]}$ - # indexvalue = "${NO_SPLITTER%s}$" % json.dumps(basejson) - # if len(returnlist) > 0: - # indexvalue = "${NO_SPLITTER%s}$" % json.dumps(returnlist) - - # print("INDEXVAL: ", indexvalue) - # return indexvalue - #except TypeError as e: - # print("TypeError inner: %s" % e) - - ## Example format: ${[]}$ - #parseditem = "${%s%s}$" % (parsersplit[cnt+1], json.dumps(returnlist)) - #print("PARSED LOOP ITEM: %s" % parseditem) - - ## FIXME: Always only does one iter here :( - #return parseditem From fa1b96c7ccaf436e500188507465267a80b1a14e Mon Sep 17 00:00:00 2001 From: Frikky Date: Tue, 20 Aug 2024 21:40:58 +0200 Subject: [PATCH 132/259] Minor fix for subflow with shuffle-backend --- active-directory/1.0.0/src/app.py | 1 + shuffle-subflow/1.1.0/src/app.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/active-directory/1.0.0/src/app.py b/active-directory/1.0.0/src/app.py index 3157dfdc..44d4438b 100644 --- a/active-directory/1.0.0/src/app.py +++ b/active-directory/1.0.0/src/app.py @@ -7,6 +7,7 @@ MODIFY_REPLACE, ALL_ATTRIBUTES, ) + from ldap3.extend.microsoft.addMembersToGroups import ad_add_members_to_groups as addUsersInGroups from ldap3.extend.microsoft.removeMembersFromGroups import ad_remove_members_from_groups as removeUsersFromGroups diff --git a/shuffle-subflow/1.1.0/src/app.py b/shuffle-subflow/1.1.0/src/app.py index ad741e68..71216808 100644 --- a/shuffle-subflow/1.1.0/src/app.py +++ b/shuffle-subflow/1.1.0/src/app.py @@ -65,6 +65,8 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" frontend_url = "https://shuffler.io" if "ngrok" in frontend_url: frontend_url = "" + if "shuffle-backend" in frontend_url: + frontend_url = "" explore_path = "%s/workflows/%s/run?authorization=%s&reference_execution=%s&source_node=%s" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"], source_node) frontend_continue_url = "%s/workflows/%s/run?authorization=%s&reference_execution=%s&answer=true&source_node=%s" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"], source_node) From f5c0f1d59f947231301b0972c65503233047f8c9 Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 4 Sep 2024 15:47:34 +0200 Subject: [PATCH 133/259] Deprecated the teams apps --- .../1.0.0/Dockerfile | 26 -- microsoft-teams-system-access/1.0.0/README.md | 33 --- microsoft-teams-system-access/1.0.0/api.yaml | 190 ------------ .../1.0.0/requirements.txt | 1 - .../1.0.0/src/app.py | 275 ------------------ microsoft-teams/1.0.0/Dockerfile | 26 -- .../1.0.0/MicrosoftTeams-image.png | Bin 104418 -> 0 bytes microsoft-teams/1.0.0/README.md | 30 -- microsoft-teams/1.0.0/api.yaml | 165 ----------- microsoft-teams/1.0.0/requirements.txt | 1 - .../src/__pycache__/teams.cpython-38.pyc | Bin 8115 -> 0 bytes .../src/__pycache__/teams.cpython-39.pyc | Bin 8135 -> 0 bytes microsoft-teams/1.0.0/src/app.py | 119 -------- microsoft-teams/1.0.0/src/teams.py | 254 ---------------- 14 files changed, 1120 deletions(-) delete mode 100644 microsoft-teams-system-access/1.0.0/Dockerfile delete mode 100644 microsoft-teams-system-access/1.0.0/README.md delete mode 100644 microsoft-teams-system-access/1.0.0/api.yaml delete mode 100644 microsoft-teams-system-access/1.0.0/requirements.txt delete mode 100644 microsoft-teams-system-access/1.0.0/src/app.py delete mode 100644 microsoft-teams/1.0.0/Dockerfile delete mode 100644 microsoft-teams/1.0.0/MicrosoftTeams-image.png delete mode 100644 microsoft-teams/1.0.0/README.md delete mode 100644 microsoft-teams/1.0.0/api.yaml delete mode 100644 microsoft-teams/1.0.0/requirements.txt delete mode 100644 microsoft-teams/1.0.0/src/__pycache__/teams.cpython-38.pyc delete mode 100644 microsoft-teams/1.0.0/src/__pycache__/teams.cpython-39.pyc delete mode 100644 microsoft-teams/1.0.0/src/app.py delete mode 100644 microsoft-teams/1.0.0/src/teams.py diff --git a/microsoft-teams-system-access/1.0.0/Dockerfile b/microsoft-teams-system-access/1.0.0/Dockerfile deleted file mode 100644 index 364e1531..00000000 --- a/microsoft-teams-system-access/1.0.0/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -# Base our app image off of the WALKOFF App SDK image -FROM frikky/shuffle:app_sdk as base - -# We're going to stage away all of the bloat from the build tools so lets create a builder stage -FROM base as builder - -# Install all alpine build tools needed for our pip installs -RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev - -# Install all of our pip packages in a single directory that we can copy to our base image later -RUN mkdir /install -WORKDIR /install -COPY requirements.txt /requirements.txt -RUN pip install --prefix="/install" -r /requirements.txt - -# Switch back to our base image and copy in all of our built packages and source code -FROM base -COPY --from=builder /install /usr/local -COPY src /app - -# Install any binary dependencies needed in our final image -# RUN apk --no-cache add --update my_binary_dependency - -# Finally, lets run our app! -WORKDIR /app -CMD python app.py --log-level DEBUG diff --git a/microsoft-teams-system-access/1.0.0/README.md b/microsoft-teams-system-access/1.0.0/README.md deleted file mode 100644 index 4ef89c3d..00000000 --- a/microsoft-teams-system-access/1.0.0/README.md +++ /dev/null @@ -1,33 +0,0 @@ -## Microsoft Security and Compliance -- An app to interact with Security and Compliance solutions from microsoft. - -## How to register app in Active Directory on Azure portal ? - -### Step 1: Go to the Azure portal - - - You'll need to go to the [Azure Portal](https://portal.azure.com/) and login. - -### Step 2: Go to the Azure Active Directory Service - -- Once you are logged into Azure, Register a new application so you can access -the Microsoft Graph API. To register a new application go to your **Azure Active Directory** -and once there go down to **App Registrations** a new window will pop up. - -### Step 3: Register a New App -- Set name of your choice. -- Select supported account type. -- You don't have to set redirect URL. - -### Step 4: Generate client secret -- Go to your application → Certificates & Secrets → New client Secret. - -## Note -- You'll need Tenant ID, Client ID & client Secret for authentication (Tenant ID & Client ID are available under application overview and for Client Secret go to Certificate & Secrets section). -- Make sure your application has adequate permissions. -- Each action may require different permission to run. To add permissions, Go to your application in azure portal → API permission → Add permission (some of the permissions will require admin consent). -- After adding permission , Grant consent. -- Be sure to use work / business account. Most of the actions are not supported on personal account. - - -## References -- To read more about required permission for each action you can refer to [Security](https://docs.microsoft.com/en-us/graph/api/resources/security-api-overview?view=graph-rest-1.0) & [compliance](https://docs.microsoft.com/en-us/graph/api/resources/complianceapioverview?view=graph-rest-beta)'s official documentation. diff --git a/microsoft-teams-system-access/1.0.0/api.yaml b/microsoft-teams-system-access/1.0.0/api.yaml deleted file mode 100644 index 857fc2a1..00000000 --- a/microsoft-teams-system-access/1.0.0/api.yaml +++ /dev/null @@ -1,190 +0,0 @@ -app_version: 1.0.0 -name: Microsoft Teams System Access -description: An app for the Microsoft teams WITHOUT delegated access -contact_info: - name: "@frikkylikeme" - url: https://frikky.com - email: frikky@shuffler.io -tags: - - Communication - - Comms - - Chat -categories: - - Comms -authentication: - required: true - parameters: - - name: tenant_id - description: The tenant of the OAuth client - example: "*****" - required: true - schema: - type: string - - name: client_id - description: The client id to use - example: "*****" - multiline: false - required: true - schema: - type: string - - name: client_secret - description: The secret key to use - multiline: false - example: "*****" - required: true - schema: - type: string -actions: - - name: list_teams - description: Returns all teams for a user - parameters: - - name: user_id - description: The user to check for - example: "b6b6c99f-bf87-4815-9f62-82aef893c634" - required: true - schema: - type: string - - name: list_members_in_team - description: Returns all members in a team - parameters: - - name: team_id - description: The team to check - example: "b6b6c99f-bf87-4815-9f62-82aef893c634" - required: true - schema: - type: string - - name: list_channels_in_team - description: Returns all channels for a team - parameters: - - name: team_id - description: The user to check for - example: "b6b6c99f-bf87-4815-9f62-82aef893c634" - required: true - schema: - type: string - - name: create_channel_in_team - description: Creates a channel in a team - parameters: - - name: team_id - description: The user to check for - example: "b6b6c99f-bf87-4815-9f62-82aef893c634" - required: true - schema: - type: string - - name: name - description: Add person to channel - example: "The coolest channel" - required: true - schema: - type: string - - name: description - description: The description to use for the channel - example: "And it really is only for cool people" - required: true - schema: - type: string - - name: add_user_to_channel - description: Adds a user to a channel - parameters: - - name: team_id - description: The user to check for - example: "b6b6c99f-bf87-4815-9f62-82aef893c634" - required: true - schema: - type: string - - name: channel_id - description: The channel ID to use - example: "b6b6c99f-bf87-4815-9f62-82aef893c634" - required: true - schema: - type: string - - name: user_id - description: The user to add - example: "b6b6c99f-bf87-4815-9f62-82aef893c634" - required: true - schema: - type: string - - name: role - description: The role to give them - required: true - options: - - member - - owner - schema: - type: string - #- name: send_message_to_channel - # description: Sends a message to a channel - # parameters: - # - name: team_id - # description: The user to check for - # example: "b6b6c99f-bf87-4815-9f62-82aef893c634" - # required: true - # schema: - # type: string - # - name: channel_id - # description: The channel ID to use - # example: "b6b6c99f-bf87-4815-9f62-82aef893c634" - # required: true - # schema: - # type: string - # - name: user_id - # description: The user ID to use - # example: "b6b6c99f-bf87-4815-9f62-82aef893c634" - # required: true - # schema: - # type: string - # - name: message - # description: The message to send - # example: "Have a nice weekend!!" - # required: true - # schema: - # type: string - - name: list_apps_in_team - description: Deletes a channel from a team - parameters: - - name: team_id - description: The user to check for - example: "b6b6c99f-bf87-4815-9f62-82aef893c634" - required: true - schema: - type: string - - name: get_app_in_team - description: Gets and app installed in a team - parameters: - - name: team_id - description: The user to check for - example: "b6b6c99f-bf87-4815-9f62-82aef893c634" - required: true - schema: - type: string - - name: app_id - description: The app ID to use - example: "b6b6c99f-bf87-4815-9f62-82aef893c634" - required: true - schema: - type: string - - name: add_webhook_to_team - description: Adds a webhook to a team - parameters: - - name: team_id - description: The user to check for - example: "b6b6c99f-bf87-4815-9f62-82aef893c634" - required: true - schema: - type: string - - name: delete_channel - description: Deletes a channel from a team - parameters: - - name: team_id - description: The user to check for - example: "b6b6c99f-bf87-4815-9f62-82aef893c634" - required: true - schema: - type: string - - name: channel_id - description: The channel ID to use - example: "b6b6c99f-bf87-4815-9f62-82aef893c634" - required: true - schema: - type: string -large_image: data:image/png;base64,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 diff --git a/microsoft-teams-system-access/1.0.0/requirements.txt b/microsoft-teams-system-access/1.0.0/requirements.txt deleted file mode 100644 index fd7d3e06..00000000 --- a/microsoft-teams-system-access/1.0.0/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -requests==2.25.1 \ No newline at end of file diff --git a/microsoft-teams-system-access/1.0.0/src/app.py b/microsoft-teams-system-access/1.0.0/src/app.py deleted file mode 100644 index 9ce1eee1..00000000 --- a/microsoft-teams-system-access/1.0.0/src/app.py +++ /dev/null @@ -1,275 +0,0 @@ -import socket -import asyncio -import time -import random -import json -import uuid -import time -import requests - -from walkoff_app_sdk.app_base import AppBase - -# Antispam -# https://protection.office.com/threatpolicy -# https://protection.office.com/antispam -# https://docs.microsoft.com/en-us/microsoft-365/security/office-365-security/configure-the-connection-filter-policy?view=o365-worldwide - -#create_url = "https://compliance.microsoft.com/api/ComplianceSearch" -#Request URL: -# https://docs.microsoft.com/en-us/information-protection/develop/overview -# https://docs.microsoft.com/en-us/graph/api/resources/ediscovery-ediscoveryapioverview?view=graph-rest-beta -# Microsoft Graph Security securityAction entity -# https://docs.microsoft.com/en-us/graph/api/resources/threatassessment-api-overview?view=graph-rest-1.0 - -# Permissions (Delegated): SecurityEvents, ThreatAssement, ThreatIndicators, Compliance -# !! Have a "report email" internally using office365 !! -# Microsoft Threat Protection -# https://security.microsoft.com/mtp/ -# https://protection.office.com/api/AcceptedDomain - -class Teams(AppBase): - __version__ = "1.0.0" - app_name = "Teams" - - def __init__(self, redis, logger, console_logger=None): - """ - Each app should have this __init__ to set up Redis and logging. - :param redis: - :param logger: - :param console_logger: - """ - super().__init__(redis, logger, console_logger) - self.graph_url = "https://graph.microsoft.com" - - def authenticate(self, tenant_id, client_id, client_secret): - s = requests.Session() - auth_url = f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token" - auth_data = { - "grant_type": "client_credentials", - "client_id": client_id, - "client_secret": client_secret, - "scope": f"{self.graph_url}/.default", - } - auth_headers = { - "Content-Type": "application/x-www-form-urlencoded", - "cache-control": "no-cache", - } - - print(f"Making request to: {auth_url}") - res = s.post(auth_url, data=auth_data, headers=auth_headers) - - # Auth failed, raise exception with the response - if res.status_code != 200: - raise ConnectionError(res.text) - - access_token = res.json().get("access_token") - s.headers = {"Authorization": f"Bearer {access_token}", "cache-control": "no-cache"} - print(s) - return s - - # ENABLE: https://protection.office.com/api/OrganizationCustomization/Enable?source=HostedContentFilterPolicy - - def list_teams(self, tenant_id, client_id, client_secret, user_id): - session = self.authenticate(tenant_id, client_id, client_secret) - graph_url = "%s/v1.0/users/%s/joinedTeams" % (self.graph_url, user_id) - - ret = session.get(graph_url) - print(ret.status_code) - print(ret.text) - try: - data = ret.json() - except: - data = ret.text - - if ret.status_code < 300: - return {"success": True, "value": data} - - return {"success": False, "reason": "Bad status code %d - expecting 200." % ret.status_code, "graph_url": graph_url, "details": data} - - def list_members_in_team(self, tenant_id, client_id, client_secret, team_id): - session = self.authenticate(tenant_id, client_id, client_secret) - graph_url = "%s/v1.0/teams/%s/members" % (self.graph_url, team_id) - - ret = session.get(graph_url) - print(ret.status_code) - print(ret.text) - try: - data = ret.json() - except: - data = ret.text - - if ret.status_code < 300: - return {"success": True, "value": data} - - return {"success": False, "reason": "Bad status code %d - expecting 200." % ret.status_code, "url": graph_url, "details": data} - - def list_channels_in_team(self, tenant_id, client_id, client_secret, team_id): - session = self.authenticate(tenant_id, client_id, client_secret) - graph_url = "%s/v1.0/teams/%s/channels" % (self.graph_url, team_id) - - ret = session.get(graph_url) - print(ret.status_code) - print(ret.text) - try: - data = ret.json() - except: - data = ret.text - - if ret.status_code < 300: - return {"success": True, "value": data} - - return {"success": False, "reason": "Bad status code %d - expecting 200." % ret.status_code, "url": graph_url, "details": data} - - def add_user_to_channel(self, tenant_id, client_id, client_secret, team_id, channel_id, user_id, role): - session = self.authenticate(tenant_id, client_id, client_secret) - graph_url = "%s/v1.0/teams/%s/channels/%s/members" % (self.graph_url, team_id, channel_id) - - data = { - "@odata.type": "#microsoft.graph.aadUserConversationMember", - "roles": [role], - "user@odata.bind": "https://graph.microsoft.com/v1.0/users('%s')" % user_id - } - - ret = session.post(graph_url, json=data) - try: - data = ret.json() - except: - data = ret.text - - if ret.status_code < 300: - return {"success": True, "value": data} - - return {"success": False, "reason": "Bad status code %d - expecting 200." % ret.status_code, "url": graph_url, "details": data} - - # Dosnt work: https://docs.microsoft.com/en-us/graph/api/chat-post-messages?view=graph-rest-beta&tabs=http - def send_message_to_channel(self, tenant_id, client_id, client_secret, team_id, channel_id, user_id, message): - session = self.authenticate(tenant_id, client_id, client_secret) - graph_url = "%s/v1.0/teams/%s/channels/%s/messages" % (self.graph_url, team_id, channel_id) - - #"createdDateTime":"2021-02-04T19:58:15.511Z", - data = { - "from":{ - "user":{ - "id":user_id, - "displayName":"Fredrik Sveum Ødegårdstuen", - "userIdentityType":"aadUser" - } - }, - "body":{ - "contentType":"html", - "content": message, - } - } - - ret = session.post(graph_url, json=data) - try: - data = ret.json() - except: - data = ret.text - - if ret.status_code < 300: - return {"success": True, "value": data} - - return {"success": False, "reason": "Bad status code %d - expecting 200." % ret.status_code, "url": graph_url, "details": data} - - def create_channel_in_team(self, tenant_id, client_id, client_secret, team_id, name, description): - session = self.authenticate(tenant_id, client_id, client_secret) - graph_url = "%s/v1.0/teams/%s/channels" % (self.graph_url, team_id) - - data = { - "displayName": name, - "description": description, - "membershipType": "standard" - } - - ret = session.post(graph_url, json=data) - try: - data = ret.json() - except: - data = ret.text - - if ret.status_code < 300: - return {"success": True, "value": data} - - return {"success": False, "reason": "Bad status code %d - expecting 200." % ret.status_code, "url": graph_url, "details": data} - - def delete_channel(self, tenant_id, client_id, client_secret, team_id, channel_id): - session = self.authenticate(tenant_id, client_id, client_secret) - graph_url = "%s/v1.0/teams/%s/channels/%s" % (self.graph_url, team_id, channel_id) - ret = session.delete(graph_url) - try: - data = ret.json() - except: - data = ret.text - - if ret.status_code < 300: - return {"success": True, "value": data} - - return {"success": False, "reason": "Bad status code %d - expecting 200." % ret.status_code, "url": graph_url, "details": data} - - def list_apps_in_team(self, tenant_id, client_id, client_secret, team_id): - session = self.authenticate(tenant_id, client_id, client_secret) - graph_url = "%s/v1.0/teams/%s/installedApps" % (self.graph_url, team_id) - ret = session.get(graph_url) - try: - data = ret.json() - except: - data = ret.text - - if ret.status_code < 300: - return {"success": True, "value": data} - - return {"success": False, "reason": "Bad status code %d - expecting 200." % ret.status_code, "url": graph_url, "details": data} - - def get_app_in_team(self, tenant_id, client_id, client_secret, team_id, app_id): - session = self.authenticate(tenant_id, client_id, client_secret) - graph_url = "%s/v1.0/teams/%s/installedApps/%s" % (self.graph_url, team_id, app_id) - ret = session.get(graph_url) - try: - data = ret.json() - except: - data = ret.text - - if ret.status_code < 300: - return {"success": True, "value": data} - - return {"success": False, "reason": "Bad status code %d - expecting 200." % ret.status_code, "url": graph_url, "details": data} - - #{ - # "id": "aa39b2f8-3c8d-4ce1-8b8b-7fe02c59ae3e", - # "externalId": null, - # "displayName": "Outgoing Webhook", - # "distributionMethod": "store" - #}, - def add_webhook_to_team(self, tenant_id, client_id, client_secret, team_id): - session = self.authenticate(tenant_id, client_id, client_secret) - #graph_url = "%s/v1.0/teams/%s/installedApps" % (self.graph_url, team_id) - graph_url = "%s/v1.0/chats/%s/installedApps" % (self.graph_url, team_id) - #POST https://graph.microsoft.com/v1.0/chats/19:ea28e88c00e94c7786b065394a61f296@thread.v2/installedApps - - - data = { - "teamsApp@odata.bind": "https://graph.microsoft.com/beta/appCatalogs/teamsApps/aa39b2f8-3c8d-4ce1-8b8b-7fe02c59ae3e" - } - - ret = session.post(graph_url, json=data) - try: - data = ret.json() - except: - data = ret.text - - if ret.status_code < 300: - return {"success": True, "value": data} - - return {"success": False, "reason": "Bad status code %d - expecting 200." % ret.status_code, "url": graph_url, "details": data} - - #POST /teams/87654321-0abc-zqf0-321456789q/installedApps - #Content-type: application/json - - #{ - # "teamsApp@odata.bind":"https://graph.microsoft.com/beta/appCatalogs/teamsApps/12345678-9abc-def0-123456789a" - #} - - -if __name__ == "__main__": - Teams.run() diff --git a/microsoft-teams/1.0.0/Dockerfile b/microsoft-teams/1.0.0/Dockerfile deleted file mode 100644 index 364e1531..00000000 --- a/microsoft-teams/1.0.0/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -# Base our app image off of the WALKOFF App SDK image -FROM frikky/shuffle:app_sdk as base - -# We're going to stage away all of the bloat from the build tools so lets create a builder stage -FROM base as builder - -# Install all alpine build tools needed for our pip installs -RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev - -# Install all of our pip packages in a single directory that we can copy to our base image later -RUN mkdir /install -WORKDIR /install -COPY requirements.txt /requirements.txt -RUN pip install --prefix="/install" -r /requirements.txt - -# Switch back to our base image and copy in all of our built packages and source code -FROM base -COPY --from=builder /install /usr/local -COPY src /app - -# Install any binary dependencies needed in our final image -# RUN apk --no-cache add --update my_binary_dependency - -# Finally, lets run our app! -WORKDIR /app -CMD python app.py --log-level DEBUG diff --git a/microsoft-teams/1.0.0/MicrosoftTeams-image.png b/microsoft-teams/1.0.0/MicrosoftTeams-image.png deleted file mode 100644 index d8986bba7f48c3c34a086ecbd36d34271645c13b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 104418 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

6aNgbd@Er@>}Y)RgjS?R5Rz{PBnO4XIG$O!+lC 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 diff --git a/microsoft-teams/1.0.0/README.md b/microsoft-teams/1.0.0/README.md deleted file mode 100644 index dba1b839..00000000 --- a/microsoft-teams/1.0.0/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# Microsoft Teams App - -The MS Teams app for sending an alert to Teams and allowing users to manage alert from Teams. - -![alt text](https://github.com/Shuffle/python-apps/blob/master/microsoft-teams/1.0.0/MicrosoftTeams-image.png?raw=true) - -## Actions - -- Send simple text -- Send rich text -- Send actionable message -- Get user input - -## Requirements - -- Microsoft Teams account. - -## Setup - -1. Go to teams section in Teams app. -2. Select the team then select channel you want to send alert to. (__All the members in same channel will be able to see and react to alert/message__). -3. Go to connectors → incoming webhook select configure. -4. Provide suitable name & picture (optional). -5. Copy webhook url and head over to shuffle. -6. Add Teams app in your workflow, use webhook url in app. - -## Note -- If you are planning on sending actionable message or get user input, you'll need to have webhook running in your workflow (Go to your workflow → Triggers select webhook and start it). -- Once you start webhook you'll see webhook url. Copy & use the same in callback_url for actionable message / user input. -- Read more about webhook [here](https://shuffler.io/docs/triggers#webhook). diff --git a/microsoft-teams/1.0.0/api.yaml b/microsoft-teams/1.0.0/api.yaml deleted file mode 100644 index 89ea36ea..00000000 --- a/microsoft-teams/1.0.0/api.yaml +++ /dev/null @@ -1,165 +0,0 @@ -app_version: 1.0.0 -name: Microsoft Teams -description: Microsoft Teams app for sending an alert to channel. -contact_info: - name: "@ShalinBhavsar" - url: https://github.com/shalin24999 - email: shalinbhavsar17@gmail.com -tags: - - Alert -categories: - - Communication -authentication: - required: true - parameters: - - name: webhook_url - description: Enter webhook of the channels you want to send message to. - example: "https://example.webhook.office.com/123" - required: true - schema: - type: string -actions: - - name: send_simple_text - description: Sends a message to Teams channel. - parameters: - - name: webhook_url - description: Enter webhook of the channels you want to send message to. - required: true - multiline: true - example: 'https://example.webhook.office.com/123' - schema: - type: string - - name: message - description: Message - required: true - multiline: true - example: 'Alert...' - schema: - type: string - returns: - schema: - type: string - - name: send_rich_text - description: Sends a rich text card to channel with link. - parameters: - - name: webhook_url - description: Enter webhook of the channels you want to send message to. - required: true - multiline: true - example: 'https://example.webhook.office.com/123' - schema: - type: string - - name: title - description: Title of the rich text card. - required: false - multiline: false - example: 'Title here' - schema: - type: string - - name: message - description: Message - required: true - multiline: true - example: 'Alert...' - schema: - type: string - - name: link_button_text - description: Text you want to print on redirect button. - required: true - multiline: false - example: 'Shuffle' - schema: - type: string - - name: link_button_url - description: Enter a url you want user to click on. - required: true - multiline: true - example: 'https://yoururlhere.com/' - schema: - type: string - returns: - schema: - type: string - - name: send_actionable_msg - description: Sends message to channel with actions. - parameters: - - name: webhook_url - description: Enter webhook of the channels you want to send message to. - required: true - multiline: true - example: 'https://example.webhook.office.com/123' - schema: - type: string - - name: title - description: Title of the rich text card. - required: false - multiline: false - example: 'Title here' - schema: - type: string - - name: message - description: Message - required: true - multiline: true - example: 'Alert...' - schema: - type: string - - name: choices - description: List of choices to select from - required: false - multiline: true - example: Choice 1,Choice 2,Choice 3 - schema: - type: string - - name: added_information - description: Some extra information to be added to the callback. E.g. an alert - required: true - multiline: true - example: '$new_ticket.ticket_id' - schema: - type: string - - name: callback_url - description: webhook url of your workflow in shuffle - required: true - multiline: false - example: 'https://example.com/123' - schema: - type: string - returns: - schema: - type: string - - name: get_user_input - description: Sends message with text field for user to input to channel. - parameters: - - name: webhook_url - description: Enter webhook of the channels you want to send message to. - required: true - multiline: true - example: 'https://example.webhook.office.com/123' - schema: - type: string - - name: title - description: Title of the rich text card. - required: false - multiline: false - example: 'Title here' - schema: - type: string - - name: message - description: Message - required: true - multiline: true - example: 'Alert...' - schema: - type: string - - name: callback_url - description: webhook url of your workflow in shuffle - required: true - multiline: false - example: 'https://example.com/123' - schema: - type: string - returns: - schema: - type: string -large_image: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAK4AAACuCAIAAAAgbqG5AAAACXBIWXMAAA7zAAAO8wEcU5k6AAAAEXRFWHRUaXRsZQBQREYgQ3JlYXRvckFevCgAAAATdEVYdEF1dGhvcgBQREYgVG9vbHMgQUcbz3cwAAAALXpUWHREZXNjcmlwdGlvbgAACJnLKCkpsNLXLy8v1ytISdMtyc/PKdZLzs8FAG6fCPGXryy4AABVtElEQVR42u29iXtURfovnn/qzujozL13ZhwJO0mAJCDKKioIjgNBIAGSIC6jM86IKC64AqKAsgphlS2GsCZAkt7T6X3fsnW6+36q3nOqq8856YSQ+H1+/nKeSj/nvKlTXV31qbferapKcurldrstFovZbA6FQkTp7++3qJfI5vF48Gi1WoPBIFEGBwfxFr2byWSI6PP56EXcECWdTovScE/EQCBAFK/XSxSUILINDAwQEVUiCiopaiKy9fb24jGbzYbDYaK4XC41VwZVJWIqlSJKNErZbN0Ody6bYymXs1rtoJitlngyQbR4PM4oFpvd4WJZeDa73W614seaYpEofUEilTSzbzDZrTbKg6u7u5u+NBqNomKg4NupJvgUPwH1JCJqTpS+vj59m1PX4EJz6buGyqc2p9JENnSNyDY0NDRc1+BfeCzRfx/anUovAgVcMhRENoICXh8lFPx+P7WOHgogPhQUcEUiER0U8tnQGRneydFonBNMzm476kUJj2ZzF6iJRIJe5FBg2bqdVjVbBkggGMViSjbkp2FgsxVAAfUHEVWixkwmk+J3FYECfgtlw7v6rhFtLroGrwsooA01iDGEAtqchq4xFFCcIRTEkBozFPDFIpsojaCAbKg0EalaoOi5AiioEmXDt+v62ISRRBQ0KGXjUMjQSKb2wic6gyEhQ1CwgSKgkM3hJ5h4ykMhFoux7rRqoGDn79ri0QQrPZMlKODCvwy5AlEEV2CIUa+enh7KRlDAzxdcQYaCvs2H4woyFEDXd40YfgZQcKsXWqqPX6g9HtGaeAcgJSJqQ0SMlV5+UTZcKBEZKCdlw4Vs9CJu6EVglvLgZ+CesqEQKk1kQwmiNJQMIijoFSoNlaQXQVTrlsIvJyIalEpj2bwel6cHRF5af1/fALLhLa/HGQ4H+bsDqeSAy+1Hcrv8PA+7QiGUxl4E/pCnty+NbG5XAHncbm8qxerW35cKhwIeF6rhC4Ui9CLoVA38CmpMUMAViIhv71Mv0eaAEf0K1FyfTW5zKk1uc9E1lIfanIgiG9pZlCbaXDQmtblozBIxaMTYEmwWmNWzWfxIouD7iM84nU6RDZCnbPLYIorD4RDZ+NhiF/5LFBSL0lATedCgZKoeqiTzT/pemc0qYysYonGJ34L5W5ONjy2TzdoZDPrZSMrm+vqHTGa72dJtMTvUoZXx+TzEIZRBA44xkLOYu60WJ7JxNovhlQkGfFQ3lZ8NZTJpvZSDcayZ2vDVIhu1OSjU5ihtuKlN0zVym8tsT25zEMGcRDa0P70LBMhTm8LPcrkSuVrEaoyEL0hVVsPvwyV/n5gCRDZ1xi2AgqiWDAWi6KEgs1lAQYioeoE3EgoTFNATajaamFnncSgw3o5xrEgvg2Da962WDou1E31JE4rPHzZbHOh1v1/hxkPpPov5gc32ADmHMv0Km/WFeWkmv99LkxGxWeoDcGaNlCOmNjQy1U0efqLNDYUhAQW0w+ihoB+llE1AQbS5AgXUFcgF3PA+voluiEiP9Im6hvgliLjEu1H1wiNYHDKLFykbLhApT4RfhqWF+aV/UeShuuESFF4YiBGqiVwaZeR5QrEoKzmIbKFYJJzgb/rjcW8o5AyFvaFIOBTtC0WGguEcEsQbpHAwFwlDK+nlJXhYzpArGgvy4uPhUJx9aRg1wa+gGsZRd/G7NL+UKkaNzGoSDIpsRJGz0acgirdEaaIxKfNwpdEroky5NH3XlIxNmdSIjSQtG4owv5oyacTPMqoyyZgBH1ug9DNl0uwwm93djshgOheL5x6YssdOOfd/b/7ki46P95g/+cz06Z6Or/aa9h2wHDtp/6U1kurNpYdy6QymNj/jXNaueCzIhEaw2WSv4Geq1GgsNhbRIMQMCD6hF9VF14woNo5GmZQ1CHrXQJmkHGOAgiymUukaaXZEKAjEFIECqqeHAmQCwT8NoQAeTjYDng3fmwxHYl3mxPWbA0eORXZ93F3X0F7b0FnbaN7cYKlttEnJWrfdXtfYVdvwYEvjvd2fuS5ezd5uHzCZoafYE/EQLy0XT/QxqJm7ICZlcxkNFEQfG0IB0pjerjAara2IBoHX9VAAUYaCsQbRzy/UADlQMzQigExEzCWYbIgosqE2PfxC7fFNIOJHIgMRSUHAhc7DI0n1RMHYRQPJ2bi4HiIKiqXShEgMIipA76JBXfxC/n71ohedrh5UgERiZCMifguV1t8/6IGg73K4euzxWG9/by7el7vZlvjg47Yt29s219s31Qc31sc2bkuwhJv6yMb6kJoi8r+QE+DYuuPWJ3vawSdgYertzbJWSuBL2e8CYwN2qW7oPKoJGpN+LGQ0akn8OpENv5p+qWgl/BZ6Uc5GXYMLP5BKE9nw+gC/RCtRY4o2Z03kdKI0oiCn6EHcUGnIhhdLihhqNLKJRjUQGoRebAQd2QitQmwk2aSIBmEymWSxEa8LsVGjQWgGjapBmLhFhMZWymTq5IPVQZMCPl3uwavNsX9/6KhtbEen1ta7N9X7N9WHXtsWVUEQwSOljQ3BAkBwiLD8DS7OMO7t3mNruZkwm70+bw+3TOSGMjlqENk4JjQIjdhIOfViI2lt1HSGWpth11Bpeq1N7hqh3Ik5q5gGMRqbHTPU8IqOUpk01CCGUyYNNQi9MqmfcRkQLHkpRzLfmtick4V5OPfdIee27bekXkdne2rRtQ2uukb3pgY3wFFb7xUJFIXI82xiycvAsS2xAUyiwbX19duHj9hc7jQZqqFMWrmRylDKkaGgb3OCwpiVSU3XGCqTos2LKZMau4KAnmxXoD5Gb6Fa4vvoRfF9oIt5TkBBo7EMp0yiWPoKJnypk59emZQNrpqpFEID5Hk126AF8oG1E+ofjGzd7ty7O01MFGhwFg5x1t+sp+vdavIWJuW/BAuWvz64qSHKSmgI8ndNH+zuwkSPbxkaGuTuCXYVVybFgCmuTAoFXq9M6rtmRGVSb1dAfipfgYJGTEW5QoTRG7rxKQzdwj5NGgQVqrFuyiKMLDYKEUbwT2GfRgkCT6iA3KCyq0KGHaYDpUGjEdU+7eNGYdZDP1/2b9vRtrHBz/pvmw9ooPENKNAsoE4KweFTiOaOzQ0BDiAgwMnwsQ0FJvD45j9vXW8NdHeH8Y3UJoKf6Q3P8pBAmwuxkV4kfkamNjEkhNgo2B64jjBji2z6NiexkUoTXSNEddHmitgoCnoUZVJv6NYok/gWGQpi0Bgqk8ICU8RmJ/sgZGXSzH2JLhdGQwa63607OSgIxNipv9VR7uWCQrAIDhhQGgQU1GwNXsFC+BTD5hqUuf3Nu9d+iQ9lRqtMUrMbeiaFZl7EHYWWEezcUJnUj9IiGgRpbcXsChqHKZU+SijQ96E0QyiIao1SmSQoaJRJRUZjUOjnRsYMLDcwFCK53MzPdO9+HGoCzQh8vney7twWUfvezyHiUZl/foLgEwclt5QIPX5CAArh0wfK9G+oT+Fzx1s3nN3DQmE4z6TsjhJtjp8GIVrTNXJggEaZlBV4Q2USBRaBAroGb5UwD43XC2qCX8LnATq+OMkvINrLLxAx0yAbiLjBW6Dgi1P8AhEUKg3/xSP5megrUIOkeuHewy98F1FQJpWGT1EaslFpyAYKSkM2ehFE5cVkL3wKTGP0BGBGTKVCfSl8e/Zme652h/W1eh+6EIxdGd/57hyfpEwZamp8O2G1hOBlSyRSVD1YQlFZakzQkPArqJVAxM+hXwooUAtTm/Mfm+Tt4XF7fDHelqzNYxGPt9vr6Q74XalkJJWM9qZigaDH63N6vPZ4IpDqjSZTzLrKC/MHA9FUIknKti+gtHmCF4eaROMR8nhRm5cIqOqVyRHdUXqNhQCI0oYTUzXSbPFBI2ZcvTtf8M8sGzQ93IFkCwVj5EoOR3NvvnsbMgEX7tj4xs3mhtC4Q0Gfdu36paOzr38A9cIkmA4GyVUhu6OGKDDCUGwU7nXWmMwyhp9vRn8TMRpJwnlmZk3uZw415lPLMYq5BykeHyRdBmIjvsJus3Q7LEZaW0LTNaTcFVMmH9EzqbErjKMySZOfxGad3KRoZVNbllmIv/3etmW7RZEDiNUrEsD4972GMWypt313yNXHZrZ+CQomGQoWVeccRplUIi3M3F7OZkAVCpFoEj5SE2JuJCiAYgEUulyJREZAgftgu5zd1uzoPZNB9YpKF1GEzwMvi2zCmSGIwsMhXhSlkXNLk024u6g0cclfQTlFNlETTTZOgassCk4YDHljcXik0jdv57Y03q/l5iOp+4MTxwlkNGzclmp4vc1sSUdjCTBqGLlR/VAgCI8VVRgdwH9XmFpJuJowzyJUQmpz/PwA+50BtGMKBOb0iiIPTK5O2BXhS4vFfHCFINQBlHC4JxZDYIcvFkUjsPLBI+F7w1tI6CzygWEy1bgP1a5JGAe0FdEghDIpi42CGeg1CKGxCLFRFmFIbARF1iCElljEUJNnVOaO/r4MFxsHwxFPpyn+znsdm7f5mHhIUwNTAsO1ZEUYVwRANdVTIELW1lt2f9re1RXHeNUrk8oMmC1oc3UGzAgfBDi8YA9uT5CxFmtnKAzWAvaeGBoM2S3tdksHZoFcFjMRJvdEKGC2W+/ClBII+OjFgcGc4EAi0kKIjZIyCauXbVSeyeLBVcWVSUMoFPdMaqCAiWY4zySTSyydvanBLPvadCgcP3XWCbMP9xpAzeuB5K9CgasDEywocPmUGR7Alq5cTWJagCucvFYQ3biIYJKhYKxMIg8XfdTwuLTb42Chl5Zu+BYGM7mBdC4cybVc95+72HP6TPe9B5luVy6Wyrk8Q12mUJfZ5/fTFDCYTifIK8uhMEjYIs+krNwpyiQ6Eh2DT/wDczMEErT7IL8AZDw6+YVeISIKIqLIhtrrs6GPqTTyM+HCNI/pirwjzE0kZQMRN0RBCU71witEREtRaaikJhveRT0HBvoGB7KBUO6d/97a1OgU1sC8lKBIDBMLBXCFzfXdfGLy7/7M6rCD/3sGBpMDg1k4MBEj6eyxocKDg0NwliFRm+PCDxwYQC8MMT+T08FpLvwi9ksH4z5/D4QDszl75lzsg0+dL/39wtyFx8qqjpVXHy+vOllWeWLO/ONzq4+vWH12x7t3Dx/3Od056Ncof6Av0O10UdNR1+BT3zVwSDmdjrGLjRqfBwmJsjvKUEwtEsUkR4FSafoQchapprp8KBvjny4EpN/HJPPLjSG4mhgngG2xwai3JgwNQlyoq3eSM3PL9juIggkFvWDsMHVA1yXtBjXmRiTGIXr7BmhuQ/gLJgBZulSCsC2OLnPA4819e9C8bOXh8sqjsxdcm1V5Y2Zl24zK+zMq23Ezc/79mfM7Zs2/P7vq9pyqy3Oqjy9c+t1Hn9y61Ra1d5O8ySIHWNi+uaDNJbERjZkp5pnUQwG/QdYgqPMAMWHwGo07CplxTz2qVyZlnXM0nsksm7MQqtXeZYp8/g3ZkfxCgRT8QAHHxDMG2LLItV3X2HHuZ28gwJvb7IB1Qe0VMo6x8Mm+/kEKcQAU+KRgQpwvmcswP2Py6zKnvjnQvWDJ0bIFp+dUX5xTdRU4mFV5a2blHTnNmt/GUuWtWVWts6t/mVN9qWzBT9WLf9yz159J51dwkNyAHqH+KtQgMhOoTD66Z5K4gqxM6mPD8aO4Ib8drdbwZtvGbQGuQHL7YMHUQFZk/4QnLp0w+2aj7cNP73n9MA84i4S50hiCqsEDoYQymYG788bd6Ia6M+VVpzgbaJ9ZdWtmdcucqmYAgo3+wlRWfQWfs6qvAg3IOav69qyqlplVV7ZsPeH2UfwVQcEG4x+HQgZ9xBbz2KyKMplRL8iDFAkJIYAoJM0SUWQjDQJETDlEwfRjUy+IqUSESEEv4oaIZA+nbLineCdaBwEKZk16EZnpRXxCpCAiqkREVFLURK2buzcVzWQHbN05hBEwkzDTHQygMBHWRgNxAZbs+iATVBtc27bfdHshn2PxxQBMhTabAwncGO5s/guYqM4oNkskHMwMsYiH3r643d4BU9PV66nFK38qqz6PvlfYQNWNmdXNhVC4qiZA4RqbOxgUWhTOwQBxc3bVhUUrDv98NTAwlBNdY7Wj/21ev28om0GCsIiW1Lqjxj2gbcJjG83daD7YVq40x6A7bGqI/89CQRgwoLPUNTy4fTdHjqFUb4yEecgJwp7INQg2WKHzq2wvBU0BPbdw6bHZ1ddngflXtajDvZmnVj7ubyDxLs8nRmH/bUFmgZIZlR2zF1ypWnzgXif1DIMCOe1IucsOt1BOtvKO3q5AAUia1VHkYZOd1PLqKKqXZnWUBgrkwBTKpGx+kOYsGwSIoWzu8BEWX8Qjz2iCUGQFpkaKHvpVoKDGOgANlnMX+7mi2w/WBQaggYKb20nRKyGYyiFUZgcQJNdyM7loxY+Y75U+RvdXEwNonlMJnt/KkxYHDArzWxUosFeuckA0M9AAT9VXFy075OhhPc/tCgyUcmwjalWCZ/yPHEjkZ8LE7OMX+gkUolMeXJjdyXEFWUHORjkpGz4pG0rDjSYbLtwTkUyZoFA24bWibEAblYYqUWnktSKiKA1mNSBk1yf3axscSmDBNo/KCfy18mD9NaAQUmJetsEh7tx7APIighO7Q0FPIt7HUgK/FP45NFQggeV2yQj0TFhEfH6X3wevWm5NzQX0PTEDJKXjlT5ulbqZUrOgcJ7RwkSEahUx81sxxZCoAalzzT+O9bhhk0AzpkSb+/mFx2HFRgxBfZydRoMwXHehFxtp3I9NbCTnVlEfRKbH5e8yDbzz3m1AQUQV5Hs9H5rwa4iNG8l5Xe8mu9ZHn9hg9oFUCzORshxb8UGAPdj7+ygeX/FBmCzBXZ+0za48zab5yht8XiAo3OLsoWV29Y1Zct/nZ41m6b5lZuX1WSrnYEoHTSuVN8qqz7638y5fAs6WgAIQ5B/hymSuRL9SB+1O1ig5zg49RESNBoHLcHWU7I6ibDIUqI9xaaCgrEeWlEmNzgkoYDIqjOzIIKy605ze8c9WikiAB3Jzo69W5gQT6Y7SQUGRVCh88r8fmKDlWixtcC4LKJgtHSYsyzVbAQXeSOlwyGcyO365EZ2/aC+GryL0KZyghXOIvKAwS5kjlJSfI6papVcUiMD2wOaX6vNMAalsr1hwtMM8wF1l/YlkPw/v6HDYzahYCfEHcGxi1PhE9xARUBVMmHH+AEuUjdgLZWOeePUihoPSxDxC7+LCfC9PEEQU2WhyoXdFNuY84eXTBCFnA5FqArd7KJTq8eW2v3NzU6NDDTdSY0xYJJK/kCsEC31UQZ3QFxRWo9o8ZRgk6WYcpkbyiEiKoPz3LovHnw4EECbopgkCwfJsevSH+QSBFs4gMCMedSIG7413u1j/seF7bU6+4/MggKwgwDHKxLDC+cfM+TchhGK+2PHOdbipMSvBkyf3YIns6oUJDAkOMVVs7BXOjOJi42gC2uRs46hBwAeR6k33pXNvvNvCxEaKQlMnCG5Z0k0WIkhJ7lH1UeN0Hh40w08QSoR0FK/s+sTOo9ywwDkCQxMSfMe0rIrERkRmwVQajXr9oVxF9SFmI6q6SXJAIRRaZCbxEFCoZhwCdieaIyCNzlt4oOUGlp66IZ2oqz1h6jBpNAgTrSaQoDDahXIaKBBiwMn17igZCiJo0dAdpY8bNo5tTLGM7/73OlY3bORLGJRYtHrVwmgEhY3cBKnBwXhYoMMEBQqq/uQLJ0EB8UW0KleGgquHL9C2mALB2KEjjorq00zar7w1DBRapJvRpwLuAgW1oursB7sfdHRhkDq42oL1wTnYOkvS6YE0v8Cv7A4nEhzrRIH0APnO4XAipdUL49jBL3QPlBBQoHPybOwi5xYu9LHIhkfkxBCnbPjEPWXDfykbWaLIMQZJgoiQDCgbpip6EdlETSgPLDTY/wDsY/cnD7DaSVnjYCAfaCaIoN5PwS2VBaN/GA4xogZBQbBQZFz7vw/2D2AYJAAFbOTjsLvgbcowow7+MgG/l/0GuwuLtmFYnFN1jkPhhqoajAcUqpmtOs9puHH6pVfOW+1YmubEWnKsGh3ozyLeSfJB9PdRDAygUHzdBTH8EVdHCZ8HZZOdC7Q/zSijmDQ+CCHnCh8E7PbmrsgXX5vIrsDlNb8U0BykOHcRxyzTmbYJrW9b0EAmGBOr2MjjprgxA8qkY/9BD/xJmAJIbMyqAW1mbvLlGsQgW6nhys1/5tvZVT+reqNmUngkKJA9ioyS3GfRNm/hEegRyWQQwQ2waNnsbIGXvCSGVpdmEANDjBdLAdlcwn8BUfAbxtEHodcgRr86qiA2HDOuyfnjMVvt9q7XtgWRVGujX3JHeRUbg5gOVImhVqVD75Bincc4a+Q9HSi5ofPMz0mTxWex3nd78lAgZVJtc2Yfu3EbgsIPvKuuj8TtxwaFWzBLo/wZ829Dj4CD+9ZdmBZiHAoOBQpmq9jRiHRc2sDMlIcCR4O67MdUfDsVEXU/Bif1aLbaMNjAjNxRls4Ll7rX1lxZtc68er1lzYZ7L9e0v7y+Y3VN58s191la30Fp7fr2/KNy08lTx5oND0DB5yuvmdbX2ccoQPAAa1pwsbm+9YE512XpsVg7PF7nMFBIQpg4fjpZseAEG7iVRaS/MSU+QTBmw/QI4grtcHEdPeHAemK0m1VwBZrIccH+Zbeztc4YgjQZY8qg2b3b7oCnjE9vaczWdn5h/hayAk3bIJKsADpECjsvjmQFXJjPKQ8+8YoseYCIG42sACLJCqCjSlQavj3DZ1oQmaDALuzdFBkaikfjuX9surRqfccqhoDbLK2/t2r9vdXr7/B0j6c2ft9Gj2uUf7WpdPnx3toNXdy39JBOai4lwA9S1+B4572bwSjfmCHN3FH0oxAkQg3O5TNft/OexZ749CsmM5JpWe3F1nGCwmXF3FR9Fc4IxbpQdXbXx9dh10XFEC/TP5CGlUnWILx8AwrrRKyOKu6OEjsajUGZhFEUUACbhan/o0+7gIDC7m8r7OA7L62/SzgA2yjsfjlb26p1LBtYBdCgj2E0kCTUR6ZGbottYvEK5m+/7wxHh6TVUaZ8QJuiwHts9rtdluiuT8xl1Wfm5Pl/69hUR2OuoLivrnDb5a1Z8xkU3nv/F683SfETYEtMmVQdk4il7AYfg+1J2BVUbsxcF0XsCrRoyxAKIoBO3qpoRLuCYZgrETWro/jadEdfbxyOHDh5r7X0vbz+YiEPUG5W1ah9zB/BD9aoHU8ziPRKm8i/Zl3bK6+ZR8Ub8lBwAgcbt8a3br/fciPIp7Y0nwF7VSjYxTYgPS6P1Xan0xz56FMLoDCz8tr4dP+wE8RVzhXa8F3/2dnMocBHKdkVEHKNBLMewqhjcT+Pxe73+7DzRm8oNBCNDkUjGSRG4YkeKQX8LGcwMBCLZjXZIuEhkY0oyCYouBfZRE4qDZ8ojQqkPKCEQ2nKA7ogEiUczWB/O9TW60u7fLnXtv28tuYOnyYeKFBY9wDdr0BhHSfW3F6z/uaamtbVNTeBG/S3DBpQwBIEFEDcsNWp82gYQYH/a/P2HswOUEk++MiKYFTYjoIBd8Af4btChWHgg4EUyIbBESkah7HVg83Evj3oR5Qi66rqcYYCDJcCCty5xaGw4PSXe+9hmyleDcTYs4qVKPZEswM7EMSTuZZbka/33f7gw2ak3Z+2fLW37atv7n2z78HOXddA2flR82df3vlq330U9OkXt/GItOvjFjxS+mD3L+9/eA3pk89vEUVkw7/oRXzinojIRkQUK7KJ0lAyKHJpe766S9mQ8CLS1/vaP/r4OtXto8/v12xpfmbFpYXLWxeuaF644sozy689s7yF31/j980Lll2sWnK+6rnTlc8dXbDkSPXiw8+uOPHSK1cADkDk5Zq7gjEQPjCPvLrJMnoobGoMcd7Q2XqbxQ4FQy7wWrBVKaAtmw9oY34fpkFcuDRYXn2Mj9pWyYMwDhoExcBxZfKyqkzCS3kSwXZMuWPzAGJnSJlERa3dnabk3m+tVc9+O618/6x5h2bOPcjTYZ7Y/ax5P6j3h0VSiVrKjIpDhv8t8u5oXilajlLh6XOPTZ97elpFE0+n1BslTS9vmjb3JDKUlh9/eubev5T+96mp/0T6a+k7ZZVfvvj3S6qocU+aTe6sfa2D6ZlkdGr0FZ8gsCQG5oT/7mpHiDpsilioQ4NNJ+UwoYFDAXsk5jq6cuXV36l+yFY+UzSPi9iohkMWQAGwszqwoViQhQdbuhUoeLx+m6O3ZvMxdMaUsktTyn+ZUn5tasWV0vKruJnCPq9OqbiMR065WvowaWrFNfqkG8M0peyK4X0RYsErhbWaVv7ztPJz0yrOsk9KhffTK86XVlzAPQPNnC+fnv7u36ZtR/rr1DeenvnB8tUXJCmSSQyr1t2GeskRMDrH5rYYlm/fvIOYgDQtn4UZFwITNgqklbL4lBcZw52WTEAvyy1ZcRDBZ1y+k11Q4wwFZm2svLFo6THsXYn1VbA9ez0hnzeMtXglg0O5t9+7OHPet6UV59QmvqxLP/N0eZj/FkuAlyGdo+3hitK8YljC9IqLBlAQj+zmAt1PLz87s+LE1DlfcsZQzwBR+s+np+1c9erlNetvKzioucMEi5pOMhWMBg11Dba9+2G0iXOV3YTQHMU4lqTggIL9FWhXURY5Fsq89/71OVVNbC6vujGrwCUt348JCvQum32uMPdm1eUtjc0mcxSKPFvjy30QiNAvudmWmTn3U/BSNlYqTs2sOIIGYqwVjLTiBO5nluPm5LS5JxilvAn3+jSt/ASS4b80acbcn8Ylj5SU70X1qJJ8LjgrErqc3zSpj+emV5xC4pj4ecbck3+dvuupaTsAhSmlb/5t6ltzq75cu+HG6pr7bHZgxok7KhRCo4BC8I1/3QvHcs5uv92CfWI7wRU0ULBZzWJXcLbw1+pENgRiXb+ZnjP/JFvXwMJTrw4DhTHICvl3WbAkEyTPfP9DqNPk5+4oDgVoEIDCtnec0+cfZX2fH0NNBcPIKJXypD42FaY8fbputh73xKZ/8b28zoV1U2qbfwQCKD/LeQEYmjr766dK/wMQTJm6Y8rU7U9P/deqvzevriEd5E4hFPwFJm22RVeAzEpK5FIjlmT0YJA5uz08vtkGZZLijGmzKVwwNMlR5ha7z26/H4t0Yz3TyrVNiGnmzLyZbAzMucyDmBGaxrlFiyYyRU4wT82pbCUhcaYa5DKLR0jzMGg4uu7MqTqz/IX9JkuvzeqkKHNEPNOq15LKpeenzztJTSP169kRU759lQ5gSbBl5V5m1BOURlHVwmqrP1OBwqkZ5d8CCk+VvjFlakPp1AZgYunKUxARSO1kEwRkBQUK+Y2balUE0CYsuIGIcKIpxYISLDZ4Xots1idvhcbd1YPYONhk8X/9nWPOwp+U4LOq64W2JtbZDCgFg751mIkj782aUdUGMJVVXaI1VWAJiAcmYxI3dUgBbaq8zZnn3HMj4qDUkC73vTw3/zpJMKFyMR2c1cwRgq5CQa1zxalZFYefmrLzb1PfnDKtHglQWLTsCNMtyQzFoVDbGNRBwcvjU2gBTGjLdtPFyzGzyTfcLlKaHdrEvo35LdbNjo6uxMq1P86Ef7KyTYl1Zjaim7NEJJJWeCyAAoturVZwIAyX01QowNGF/y5fdSYUJgNXmvahxfQEKKASJWMbWwWzQHnTRM8CRZNGFBgBCjpB8tTMuT/8pXQXhEdSJZ6a+u4zy44zAwPsUTBP1bQJsbGWSYUedVcv2t2NuZ1gWPzl+hAEcOhmmHQxzrCcV4WCftcVeW+5PgoewebhfGmlDSsg5i06Sr5ECnvHBM8NRDdmVP0yMx/TZjBBEBSUgEfBKpgL6hdeWgsCG386g1jDNO0Iow1zfRQc8PZt+p9Oo5vUyvPzglJz0iNkKEzdgaRCoR2yAksqFJT9e/g+j8penvWRzfU9O95uv3wVAThsh2IgYGAw1T+A80t6aIk0xEasGgeTgBlXLP0eUC/M1nD/QrAAFPAi1lzDyvfpl52zq05Qpwr5kaZ/tlDOGAp5PpEPf1WsjTzAteoeAqm/3t+B7Q9xkgWt1IbVn9eCxSjhuUTblKNjEjTOlKFWwK4vqOnXnyBGlwpFYxUKO5+a+tbfpjVM4bLCwmWHuRLB3Zs1N4WsoIRIsT3e2BqHuu3393zV7XBiC5weHBhht5myqnOhu9s+mu2+EVPOIzwQBBslpo1zabAV+RvvXIMdGqHuvBdbddHu8tSgBEAXSBX5nIoUiYjnt/59c5AtpR30+1wk0GA5Bi1FV2IbRy0nNg3bsgZQ+PVwoJVRpCmgdHhZZ2r5aQGFWRUSFJis8Cag8DKHAlmjJSiwJdKKA/rfba23e7EQEZthIZiDz7gGZ0fJUNCfHcU367MVnh3FmDZ2bNnx5tXyqiYu691RzAPVV2dUXSsMcJKTMEM1q6GRDArTqtrnLDiz7fVzKWXKymg268OX8tVR1hJVWtR3fJPhY6nxCDslrLxcdKBHpr4zU8REpiJQLi1W/6Yp5WdKmfFRTBDvKhNE6b8XLjvF7QosvIUCF6Alsh1csdF3Q+c7/7acuzCAPQ+wr14o7AtH/PzwDSwaSNDpHuKQEvloEDr2Qz6QQz0ZJaaevBLhKYANlzBfABsf7r6H2Z2ZCNngvsU5xC0hGM7Sroti+gWfVm5wk+ItZX3VwhMffHIfJylhQyjmBQvSYSHsRBPa2EqtW2z0skKT1qiQZ86nps/7aRrvdQGCAjRMaFIAUSgSGsBak6cJP2Hq3PNMg5h7EM6Ip6dzsXHqm7hfsOQ0tyt0QGxcu/7m3zfcrNt+r+GN1k8/f3C3HXuU5OAQ9/v4aU82BA57JC+8acR9aOWAjCI7tIHB4OCJE6fti1ceAnufVXUXvgO+mlbyV2lnDZIZb82Yf5fWQj2z/MSlZu8gV2yx7oXHJNI+ihlaVE9hisqi+rGJjaWFmjoU9FJm/L/Abvi9SJL0MEFJdS7MHZ3cUC7bFc7Bsjlz7t5//vtGzab9y1a+O7eyYeac15es3PuPTec31Te/+e7dzz43HT0R+vlqqr0jjXVOmFb50qJezLgsVNXsgIuBWhabHYn9+DVh+yDqDwHAJUOBXizYdcWKRVSOtvupj/ZYqhefgnUIIdG0lp73+g2R1KVRAMclLK6C8aDq2aPvvn+37QHfM5Afi2WzUnSgCXu/yUGEYuFayRi0BmlCaRJQEH0j7gkWEw0F/u0KNCXjwYgcrokkG2ZZL/sYS4zjCYRhZrAhSSA05PKmezyDYKogJhPMd9/jxjl8YTiaESbT3x/t7cN+gwG+TauPn2iYpMR3S8X5iC7aexYXnRpIu7mKQxNpK1dc4gxC7MOHt3D8H7Jhn2KeLeX19fBzTRBKknN5c1/t7Vz1yo/lVQfnLjyp7LtAMmPldWad5DutVCw8uHz1oc+/bsfKwR5Xn8efQIhXH4zeKQR5hKl6gIJ8oCNtjYtqPDQUuCMnz29nVZ58/uXzK1/+iaczPJ3m6czzSGtOq8SJSs+vuTxjnsZwPhr1AUbx8wSFWRUfgoUiuAiLXJEQEs73vkumEh6idHd3cUWc8W34c+E1gI+f74nKDTXxqBqfa8sNe2BQBkAx0iCczCdhNgMKxFoKQrqVQ5IzflSEbTblBgPq8eZ+Opv88FPbxq2ta9ZfffGVi+s2t259/e4HH9v2H/Jeu97fZcX5nLSrbf9gOkVHJ4p9tcGThj0w6OH0BTbgMBaZu4hG/HPPn+wdZDEaCOZEg8LMwm6Gcv1D+fsJTamB3OLlh+CU4kzoXN4ZYeyAILg0MQ2CcwW4qmdVfNDPlgd1cwPR/UQyTFMAVoBySoez20LLAggKVlsPhwLteJXmM65tOCjwgDaEq2DrtYQGCugVQEHarC9jsMMQRwNtNoVYQ2znyUIRs1h3l+s0RU2WuMmSVJoCe7h4+0xWD+qGXZ85dhFz3MugwO1IfLM+sd23aZRQKCqWY/rnTktyCmPbMB7OiMjEILUglmLz1KveJyY0AevLVuyBQ5WJJhWKq2lqWd78VWqg9SgTxNS5F/FDZlR8yKBg9fJISVsiOaAcT57o5wyg29HtJi0RnQeDAW2uBijQkRPiRDl5aRAtFdesAdef4ECnoMrKJA8tZqek8xOuadNNhJ0GqEfZmap8EfTgYBTxsXbrLYftNmoKHoZvwCZ87MxMHH0ZCFHdBgaHiEJbKGm28BwlVxheYCyEwpIXfuRQCPJeJ4tmjHdSP08JXssJTAiYXbbiC4ICH+VnVDngnICCbGnQuaNOzCrf3WXOqfG5OcRxYFMfjC2fP0KYALcjm4y6EWaaNygP97XaudgoVAPbiPvQynG8eg2i8JQYM51AlOLoVPa04Eff9PSYaRZDstvb+UR2H3vBU0gtJi/U38rAyc+1yg7RrngkNuqXISk7tD2soIAWp/gAUhCWvHCSdBW0GcLPs7QNIe1E/askrKFevGI/+LyiTZSdVlQJ2dBUAIW8qZGc1IACdsekbXLQ79j6BCtc2fa4ykLjNNistD1ulpg2rLZgD+gtFqvNKRkWQm57qIOI1ZMsgBjDg4iVjVj5dt8MgjgGh++IIO38zrb+c9PO7+BnVFo8FkL9+Xbf9iFlrz4crE74sxXu/M4CZ8YEBdaCAgqcK6w8MsgaMcYRqqyf4PVWFkmoZ7wTw6DHEa+M9NZwSRmd4O2Ln/9qxrzjrGJlF/JmBsXX0FRUgzg3o+zEjPI9D6y5viE2BwMQPg8Oqo5hr2jss4fZB3ts9g/msBMg7MEd5hRbC8tlIEStdpj7ukx9bg97RDZsqNxlGgSlsxPnLyjZfP6cydSH5OxRKGBjnV29SMiJRYcDGTWbOf3A1O9wqdmGQEFpAzhSIKFmw/wACpLdoc2GFI0pFPge8djZ1W+zZ0U2i3UQvwJ1xmpJhvvsIDQesBMeTeMcizI5texiAVdYeWxwiLo8QyAV2ykq93zEKGvERoECJZv0ipy0pWVZPy1esRdcgbOri7J2YyDxSB5t4Y6aNffblS/vf3XDZ6/WvL9u/c41r3y4teGHd//zM1YKvL+r5T8ftu788NaHu9twNCXSZ1+Z9nzRhfTRZ/fxuHN3O1Zwf/qlGfRPvjB9uPsBiLs+uvv5lywbI372AO+y9Ok9ehf/ypf2eefuPZ0oZPtbl9dvPLl+46kNm8/U1V+kVLPpNChIm7eer224iIQbIr5Wq82GT/yXKJu2nKMXN2xu0pV2ckvDBfyiMxdCUEZwPhlTK1MxCLCPwhUUKKQztLdPZihnDAXl7L2sMokMwwYyMhSG6349FGDLWUJQyGsQhlDIhzkpjjTlvglx0rAwsoC2aXVIsD0vWn5wbc21NTUta2uaX17/yysbbm9ptCOOmaVGG6IX8yfUNjjqVLpyzzPIeXDPXlfvxX83N+CsSyt/i1Fqai3PLLlaUXWeEiwH45XKF5zDcih2XwXr01mezs+ublq2+kxzaxoxV0PpAfhQHhoKU/JcQREb08oEkVA7lXSHjHrW52A+ZQeEVjbS7DBolAzokFSWPP/F9LlHBBQYY5DdjxojKYdC3kYy9wxEzr9M+ajAB7HkBFsvRfEK67Gm1rq5no4ljZFnklyUwlH52rYwkqDgnu8dGRUZNPnleznh8NMlL1hmz7tZVnlX8SPwpHgfdAlbts6ovDncf/PZCguhDUFnVN2Bz6Ji4Y8Hf3SAs44FCqRBIKiVop9VKIQ4GqgXSWugnutVVYl+FSIjiQtZ6u9+6d1eNfXrUi9BIa9BlKtQ0Mc6cyhAz9R4JvPuKCV05Z/YPlNZiM2WVSG2sQt7qSCqnXbaEmfMiT0bNMfPSSfQGSTNv/hbSmQUtv7AjgCLlj+YM/+Wugljq2xgHlNqlW7oXimceSuqfilf8MNPTbByJh8eCjwwmkVIl1+exiaIE/1pWivOTuPFQswhbgNhN1yhyFPU/2alGSGrExRI+xgadepjssKBmRVMg5hadr7AO2Xoti6ECIcCJoidPMyVnNRvPbPsyMsb7rDwRiVe4Z5YByHvwSCfRrex3qXdm6FBczhdwaN0r55Vx25YhNyGLT51Wy7jpC6Y0d7rkxol26wsnlTvadsNHvmI/YIvPff8QURpj0FsFKYbZm2seq5p/8HIt4ed3x5yYeHft4e87OaQa/9hN+6/P+jafwhE14HD7v2HgkjfHTYzMwktGcsVwILDIBNJ5L7/4T4ODT5wKLBflHYQn25K3x1knyiff4V7//epykXHeAC+3vDcNEyMqxYKf536gYhXwM3CpT8CCsrCmJrCJTG6/takugZfESiwddkMQNp/FRxnWO9/biV2b6SltCN0tkHfG/9Ljn0VAQ18N+jqqxULju87HC0ZWzyj1MrgEEenz8VqNZHY+rXp8/D5A6JCps09RqvYWG+xdv+009JPU4kKhbQ0ZWS6/blZc9+bWXFgetmRaRU/ohy+hu4HSAPSV+BRSTPKj/KSm4YJvCgWwqQNaGMTRANunlmGCeLuKnW53NoNJr4SUruXj7Sjj7zd08Ml3XaCLK2p6dYtrW/l+3fqI1byiWcouNEGO/GtPQvzsJiGsgUX/r6po+QRAlyLA0WEilwQYiZWtE2v2NdpJh5gDAWHPzdz7scwAiomI2ONYISIldIiAVeFiyYMobBw6VG+jlaBAmQFCQrUc0F5a0jNhl8PCQW/Hkk1W4J6KEh7+z5s0kdIy6GwzbMrr1Q+d71kvLtfF4tczqCAJVaGUNBokowr+DgUhPVQGI9HDYVigXd6xVKVFURso8IVNuT335AWyuk2fxyH8yMMdn/asMU/55E26GsZZmVEvih1KiF+c33OgtYxQqFUh4PCEPj8v1SuQD6LqzPKCQrKBKGeiKKDgo4rlBpBwTDUvejUoLU2GkKBcQWNrKCHQr3RjpCjECYM+l66r1Wg4FPm8vFMGqGBBAsGBeAAaBg7FEqLjz91FCrK59yTTPks+wVLkQQUhD9egkLa4cvNmPexYsJi2ooIlDpXqhvf08cYuT8cFIQyKU8QxBVCulMrg+N7foRcLLiCiFcentuPMrVKsW7a19naOmVF3vWJkBUKAFEIhWsjQYFxBQ6FE0owXMFKprPaFfLlZ0cK0B0BCtMKuQKXFd4iKKwaCxSC4wmF/Cbej7JBU6tYODXTQG5QJqCxQKF0FAukhHJfqsgKJ6hraYIwmch9mc7JNoasPEF8xGUF2g7hlKEhuTDcWQlKKJ3b9FBQoIVyhVAwUiY1ssJwM/0jigsShU8QzZoFk0UWzhZN+Z2ejYQGqBU0TbSUjJ/MOIwGofgs8lCQZQUdFNLwkahio+IjkExGem3CsO9HufxXhsIuiSsoyqSR2DgxxwjoYFQIhdZxEh4LxEaBAJHGEttoIJbr1yqpjh/s2MJD4EmDuDa9QkwQg3oTE6DQ7VXERiErKK4j7cLtIrGsTUUD8jRQaJKVSSyqhwWaG57zmzIZTRBFOPyYJYY81AomCO1BDw9/CIDB6616zWLCZAXJZ5F3XzFZYZ9OVkiPrEE86hrfYaWZkZTJNgNlcuJTXmwcxw0cR0qTUJiEwiQURmVXmITCJFeYhMIkFCa5wiQUJrnCJBQmucIkFCa5wiQUJqEwCYVJKExCYRIKk1CYhMIkFCahMAmFSShMQmESCpNQmITCJBQmrY2TUJjkCpNQmOQKk1CYhMIkFCahMAmFSShMQmFSbJyEwiRXmITCJFeYhMIkV5iEwiRXmITCJFeYhMIkFCahMDlBTEJhkitMQmGSK/zmofCQ++MpULis7rrCt+Wq+MZsifNTZbzqoT9BnmI8hbCF58x5O2lTfzoAiG+8UriJxyNvE6PuA32Bttr481RlWy6+1YbYluu3AYXhSijYhmdO5UNv1vfQUGD7Liu7rvzMYFF+8EEXO/ZqIM0OeEEaoDTIUzpnd+eml39Fx5SJndynjzNXyB8uqB4CUAAFaQOe//9whdYRjxxtekQoSNtw0g49J55bfmH5i03LXvxp6Yunl77YxNNpNTUtef7SjPLjSn6+V+9DbM74UBsI6SYI4EBwhbU1v70JoqAcsRcT27+zsqW8+iG5wjDn74xwRKm4n1FxRpzJYXSy/QU+TAugObX8zPiLCMWg0PCbhoJBUXP4xr4LltwoGa+JoAgURHdKx7MUP032XOmo5b5xggLbluu3DoVhd26bU/nzCy/fLSktvjXrI6eCYS0d/qom9RRz9eR5w8oY84ZxgUK5vEObpExqd3P9jUEBu/peVzf9xiHG52vqOiYcCuq2mpqz/TRoaBrmPPkJmCOGhUKhMvkbhAI/SqS6YMpgR5sDCgtOv/4v88TbFeY2KceAij1XK84VigsFKS9MjFV9fSgoGG3s+1vlCs2F+4A2syMh+NEgcxYc+eJbbwlJgiMfqvGoMoS8A+85470/5ePeKiY+4SDiMrCcn3D6iGpXUKGw/NhvGgqt6j3jCjjifu7CA7fa2IFBBSNvJG7c9Gja/AgGK1Vi+JWgwA6rh5gCE1Phxr4MCr8ta6O6x7NSJts1uPrqDBwaU30bR8Q8v+oQTnkvMer+CWDLRpqFrsubxqCsPvIE0aSBwl+m/XPBsh9/Q1BoLtwzXIECzptgR8tV3sEplP/+oAUHspVMm3e+tOJXGYVFzox49H596FeaaILghuf8iXIEhYXLjxQ7Jeb/k1BQJgXlVIhqdowYzqicMf92efXxS9dCmUy65Jnnm7kMX8AbJkanUBVFvbT4a0NBWD9xQMGpqWX7/jL1P3/LHw1SeAjAbxIKxCHYRuLNazdcYkeOWttLXn+3kx33Nve0fNqauiv6ODLkvAYx4sljDyF5jAkK/LzRpqlzwQ5x+uyxp6bv/mvp2+zoWe6DUM6ZxNEgNb8NKAybwBhwrODZCykc5mwzt5Xcblc33a/QnNV0boKniaaiUGiaMK5wgYwZKtwPP/a/txM/ABoUKIArKKfE/A9AYeM2X02db1yh0KzhDfxsgVuQEl7+x5G+gRxO8LVaTCX2nhyOSccpjlMrLgmn8PhDoUifFf5rel6fbBr2MLgxmxo5FOAKIShATvq/0778X3/YiMNhmMzIoLBjSulbi4gr/E9AATgYfygoh87yovgZtCQwVlSfPtnkdbkDHo+rty9dYrF2dHR5NtbhWM+DU8ouPV1+4+ny6zjDA7EFPF3DiYB4nFJ2nR5V+i9T5jSLx6nlLYKuS9coTS1vFvfDpSlz+Hfx064p4WSRqRVXxKOUrvKKXTX6V7GE0qaxCJorsyuOT5m+8/En/vHYE38nlvDU1HphYlqz/jZkhVXr7g5/dtSIB0E9RKKD5FTAhV59zQNvof54IJVYYCego8Dkg4tVk2IrHUQmjh1m55RXteCUY3bQcfX5uoZrJnPUZmvr7u5CDBGg0Gk29zww9X/znW3h0m+nlX01c953gMUsJX3HHilxykw5caLxo7if993DJXZ47Q84bnzW3OP4FAl8ix1AW/iImV7OM5o0o4KVP73swP/5239+94fXHn/yFSQ+LzBZ4enpr5PhmfMDWVYockrMOKRa6RApnFG8tsYxJ28OEieE6o4Fq27RH0KqEpvnqImOmsFNWfUVLjkyCyPkIZMth67HGfV2uxUBZCXhcDAcjoZCqWA43eNOn71g+/Kbm1/va0f6/KvbOz+88v6uyzs/vPTV3jtf77v79d77uz9t+eCjq6B//Nl1yvbF13d4nitIX+1tQx4QkY0ouKFsX35zlyjIjHsiohAi4oa9u6/9q286Fi76+Kkp//rrlPd4+he//9efS9mnSHjUUEaZ/jLln3/8c/3v/lDz+ydfffzJNb9/4qUn//c/FChM3fH0tDdwYNDilT9xENz7Vc6OCgoobFShsOpVS+HZUa3S0YAjQEHmGWQ/ADPAdMDuqy/RAfVzF548eyEQCsYjYT96PxxJAAYlIqjQ4+4Gh7BYbMFgb5YfMp/qy5lNAbPJZzZ7cXp8lp8z7/Iku8w+JF+gP80pkDuIgoTwJDpA3uPrJYrbm1KOlDfKhkKIgmKpNIgwr23e89gTa9Fbj/1hHbj3Y3949XdPrPvdE/h8VX1UEu4fOqGEJ15+7MmXHnvyBfb5xMt//mvtFI4DSlOm/3fF6nM6rjCRx4jxqYF/BSEjtGyVSY4nmKNzH+gcCi26U+Ra1WMqJWLljdlVN8sWnN695wHa3O/roQjToXTOArGRnQCLfmZQYLwCkmQoFOLEXH//IJBh4ZeKmJzH4+NEICZMlMHBIbPZSsQMj1fFu0CK1WoH3e8PKgeEpTOUDXTcEzEQCNGLyE8UVOuDDw8/jn5ifbb2sSdXsZ7DDd3zx989yRKnjyk9uer3T7z4uz++9LsnVz3+p3VPTW38Wx4Kb5XN27P8pfNr1v9KdoXaQg6xuSG8aVvkmeXt+WACLRSadVAwPlBQHEPOBIXqy5gXplfenV3Z9K/3W03mMPrU63UrbT40hMeSPuUaQK+4XB632xuJRIiUTCbd7OpB6u/vB6W3tzcYDLpcLlDD4TBlS6VSeCQiMhAR2fi7bgBLzkYX7omIQuhFka2vP3HqdOtjTywD9+Z9thJj9/E/rlbHMRvK6EX++BIHx0sPmVY9/oQCqd//8ZW/TNkK+YCD4M2nSt+YWb6rctGB5S9dlLhC26+oQShfUfHMVfUg4tZRnD5rmKdZkRal82tnVzf958N2jz9DfRoKxtDv/X2p3l50tLfEol6g0lmPkUiMKD09PYIZ4NFsNuMT+CBKLBYjitPpFNlsNhvlTCQSRInH41Saw+HQZMOFQoiCYolit3fEotnfP/7cY3+Qe3qVDAVxwzsVnyyJztY/Ft4zKCA98adX/1y69SmuOzw9DabGd2bP/WjBs/srnzm07KVLq2ruqCamtrUbusbnJHJD7bFBuq/34Itq6tyzKy8VQKHyBsb0TIOD61sL5oKCYwhVKLB54c6cyhsVC058tOdOlyUcS8RptUEikbJarbzN7UxsVJFg6u/vpSMfwRXwjEyAAp872EXvyFCIRqNE6e7uFn1M2fCphwJ9H12AhQYKyK9mu58ZylUv3P7En9Y98cf1lB7/E9I6UPgNu6fHJ/644XE1GxE1j/p/Pf5/av70fzf89W9bICE+PePtp6f/c8qMd2aUvT934ZeVi/azBCisuggcEBSgT04oFPKHTDKZ0Y2bVa+6mKBHQSVVxB4kfXKYqUGREBUTQguHDpcWqy+VVV18dunp4yf9FrPfauuKx6PUqckExryJt3m3AgUMYgxT8GaSH6PRMPUK+Lamj/EmeLumj2WugGxUmsgm+ljOJqCAQoiCmYV4jL37AYTHPV/eWPDsnqpnvq5e9I2a9kn3Y0/zn/um8tlvFjy7t+q5/ZXP7kPiJQMEB3jCzfcSFGBaaP/HZtvmRp+q90/YvKAcRxxavNKCcQxfEdmGFTRUq8nwtGH+Lwk3LBBhRtWtGVVwPB6q33HNG8jZrT4r63iTGKWpZFyFAucKonswW0CgQ19i/iYK5AOremUyiqDn8/mIQtIlFxsH9dn8fj9RAoGAKjamRTbIKUSESEEUFKuIMJmUyeI7edoJKKBv5i88OH/RISQMVk3i9O/lG4kuZ9P+CxRGlFLVswf5DYPCfA4FVVZoW73uwcZ618RyBQ4Cwlldoxvh//AWllWfFwfQsvFdRXyi1SBJeRB/wH3QV8uqz5YtOPH8msuHjnR3mROYkDMZPjizJKozTKgSfQbdga6XoeA2M9iYRR8DCjRSCzUID1HQiwIKREFmQIHmFHQtUaiPQRTZcAEWqgYRoAnF6/USJZsZYCavjuTiZR9h4IrOq9T03zMFPSr/S9PNmkeGCQ0lnxhjkKAAu8K99XUOxrcnEArB2no3Es0R775vbX+Q++Sza0uf31dedQrBqIzVg0lUteGThrucyJsALjKzkmWYOb8VM0J51Y9r/nHs1BlPIgkjkpdpbZaOoUwfb16M0iBBIT/8GBSsChTQVRApKYcMBaIgn4AC+szMri4JCv2ijzlXyAgo4AJ7oPKJKxCRuAKIgALNKaJa2aGc1QwMud9692jlc18oOOCcXOLhlDSPo0rVUlJLUBBQ9ex3EhQYP1hXa1NwMJFQEOXXNlpPng7ht6OJOjsT5y9nt77eXvnc0TnVx2dXnStb8DMcSHOqL86Cpaj60hyeZlddKK/+GSykrPpMWdWxZ5efanz7zuVrgzDPMONQOm613ufD1DE0lCVZkDNsO2g+n4c6i0PBXAJm4OEXphBM8JizIcqRyod3kvwCHRmg9eETs3uKXxAb6UVkIz2TstFF2UBEsVQaMERF4ULHExGFEAX5kYFl84T6++J9vcnbdwPzn/l4/qLDbBxzKBCrr36G99yz+Pwe9/zmAI1p6f573A/zr/14SxRC5RDzoEeAb8XqK2tf69iw1aV00sTKjIrYWNvg2rrjltkGZQoKeaS3t7/HnXK6BrusA5ebw1983dmw4+pLa49ULUYl989b9C2qWv3c90te+PGV9T+9/vaVz79uu3s/HUvlYnFI9ENulx89Fgg4U70xprr3pmG5wWiH9QjGRRBAjMUiomugT5aYLF1mq6JM5hRlMmKoTGo0CGQbTpnEVVyZhJyiVyapNKsNXzoIloQllOs2Hlm0/PiiZT89u+y4eoN0AmnR8hP88QSjLz+hJvlefVz2U+G/jooSnlnBilUfcX8U6bkVpzds6drc6JG9hROHA0wNm+ujG+sjdY3mjz+74+wZUJW2DMJJ7NZbVuvdVCoBIjhpKJx7YMq1d2TxGU3mEn25/nTObBnoMoVMZkcyFUFDIsXjfsbLbU5bt08pLJeGo4GseYmYokzGEynqeqFMmqhXAMOHggJ6cThlUoaCpCXaNVBA3+vtCjarky+zH8SPP/Rj1/IXfliy8sySlef4J92ckx7Hnha/QOm0QsHNC6dBWbnml82Nbt79QUMn8jhDocG1sT722rbolu1tFy71uHr8JNzxNr8HHAAQvakobxN0TR8EKZPJ5XSGmCsgm8OEbLE6WR9bO5PJIIdCbzwWZM2L0dft0UMBgzObHZK7BgOYQSEY9Af5FYlFKeEiClQJ3KO38AkBgohR6SIioCMoQfUSFLwul0YX7pUvhT2Ll49PKi0UQhXCSJFoyuXOrf77gSXP/7Rk5QXeW6eW6Hpuqdq1S9UkP+r/tVSHBgkf5xatvLiuzsJ8QtsKPA4TxxgAhU3bYmAM7+20wfgei/liMU8sEo1FU7AG+v3xYLAfCIhG42g2pFDQEwp64UaKxYPRWACJt2U4EIwhG6VQOM5aEtkinliUOhEOJ/SOPxQKSP0VlromXKBBEEaE2AgJQDCD0WgQqtiY04uNpEwOp0GAPQgNIpMdAGMAogFzWL2OnGhb+uIPi1deWrzy5yUvnGRJ6dFTSAoyOCyUJAGFY0W5WbqSP754ir2L9doMVez1pS+c5RlOLXnx7HPPX3ylprPukW3MtVrPQsG/qPyNDewr6rbxVO/e0njvxu0M8/v0JTiftsFfQ1KeouezydMEZ6Ik0SuXMAOKNhcKvNw1+myyBsF8EJo+Rq8UahBaKKDPDKFAU4MeCuL7CAqUTYYCUQqUSZOdQ6GjfyCZSOXWrDu65IWLi8EYeE8DE4uJSTBAXFr8wjkkzjYu8BvlHjf0r8XSvfiXNr1wcemLl/+xsauuwVM7ccaDQkcDzUGbtyTrGhw7d7dDNsqy4Zcini23uRilos2HgwI1piEUhC0Hg9Owa0pQ6MDAAN7BPyAAQj4AFPr5BSHTyS8QkYeIKIiI4DNEhJrQo164p9JQaXoRN/QieIxTvZjXiX8vZcOFGyptoD/Z0+N2dnucPbZUEo6x3NkLoedXn4RUv2J184pVLStWtS5b3bLs5Wt4fP6l1qUvX8MjiPgXuwFxVcvzLFs+sfwsTwvyswyrry9/iSVQlq+69sKaG3/f0LF5mwuBAkiPGI9EL8qehdphAteUzFv7wRJutyX7+ocGBiDYo5Vcos0HBtL4E20OTk6NiWkeIhplQ2tT06EN8UiNSc0r2lz0oNzmQAyVhl7DY4kwIuG5iNgo5EFhUZZ9EAKYGg0C9CJio6E7ikQYupxO2KdtXabo+x/drttu29QYggG4rrEHYt2mRh+/d2/a7oa0j9GMT0q41zxK93jLze99POHezS08/s1bJ8b3yEJRtA6nAqg1eL/a5/YH0+CCmBMxFyjWFaZBkKBn0OaGXSNEdUOJXrQ5Fxuzeole9kz2PZQGgWoVUSZFNkNl0tAdpSqTDi4ts5CKbqcHIRSgt93vq21sh8a1sT7E5KwGN24w3XIjnd9A9S/yqLnPd0xwQuPVFDag/UZ/w9st3mAOLnoYBOErgu2GDyEGBbPSxZbepIBCWO/QEYMZbU59TFAARIaDgqFyV0Lv4BJQENCDTUkPPcE8xPdpoECl6ZXJ4lAA8BWuwLxkDAr4Tc4eL6Bgsz2IhIdOneutbbTlPTeFA0vTvsVDiic2ZLlhWC+D9rHBCyZx7bobPzUUTlBjqm2ekdscEQWkXgooyKPUcPiRU1CGgt4FqIVCll8Q9yA20neDK5ArQXZHEdzwKfxMECnoRcxVVBtyR1GBQh7EzETvatxRlE24o1AsUfAvjicziKgAezc7BGXHZEn+d1d7bYMTqhc31jJTIHQw1qAsFW163YTNPI16PI0TFAzQUGC1JDS7ahvNO3cHYFKzWLtD4Sj9fNHmaISsekGmpsakNieHjsgm2py6BhQSG+Vsos2RWXQN8tO71ObFlMnhNAjiSA+lTJIPwlCapdLyymSGuyrgQLF2DqZ7yd4SDkHntN2+E339rXa2UgDBx2yOcG1i08TwTV/E4G/033HxQRvWB3MZ0x4VD2QIM92mRufb/757vxNDHz/fJLzBBQp8JqtR4A27ZpTKZBENQqtMEhRGVCapWuit4lAQOqdGYzG0KxQqkyKbCSKwgILN2mUyeS5fi2/dfp+z1pDSrxrm3MjjRYsM8Ql3KBh8HUFBrRiTcrbueGDtzsHjQj9VhUJGQAEIyWU1XQMXoDKuqGtocIuuQVMTe5ahQCxBDwU5MAC9xqDgUy9MIeR8gqxAFJRIPioQiYIOw+yuyQZMJPglsuES2XBDFNSA8uBCyVQaCtFnwzdSTnjWC7P1RmK5E6fcdQ0PWINibDHOzzVA1d9PaBgpmvRXTUw6UfhQkC+mQFBCx9mLMRc8gEk3/6W9mAHVxmQuwBRvJzRF4c9nBke5a+TGlNscU7zIllQvP79ABOyoT9Hjor/wmNcgiiuTRXwQEBuHUyZH747Ki402ByYvMpLDwEBWNlRJDBq4Xg4c7NnSYGU2OygUKhSKTdX5hQb+X23poySO+NWIBLauBm6nw0ediD3H70JMLzWdaHMmNmaFD8JCHiJJa9OKjXhdFhtlDUJuc9BHFhv1yiRQQ1zlUZRJ8X0P65mU7QqoALEyAYXevjgMkR1dsW/22+rqO/mYU4baqLzJw2UYUR0dhzhmbmluNJ8+kzJbImRF6O0bEm1eqEEobU68XW9XkNtcZNPYFR5amQyoF/mEcKHdwfNBgdCAR7yJT5GNHEi4UHswHFCQuUg23OCRcsbUS2RDISIb5cGFmQruE/JjSdkiammYRYJef+67Q666hntw603oErZHNDMr0xZZQRo6jxz3wtHMf5OPt3kcLmNqTPql5MkjF51okHxjhvLZRGMS56ds1AtyacXbXBDxOEax0VCD0MQ2ygFtQkzViI3DiDA2srLBSkrEUIikSxs3xuEr+sEHO03Jwz866hpsTMMcaRDnZ41fQWwkm8G2AAm2bGqA6ri968cToS4z5N/OXHaQJlS3y05BXMYahF65C0yID6JAg0BxRTQW1FVdRDUqzySpwsU9k1Sa0CA00ixdBAXkRJX49GHD2iwqDJ56BGvAo/nTmWDDjpu1DQ7u6wuOPE1MMBRkExYTXOph7bY1vnGn5QaWBAYg+kAVyvexy0lzMUEBvxQSWxEoiD6Ww05lKBBRtivolbthoTCoXsiBqQXTM6pFLiVMUeTzwIXOo2z4Gsz6oKB7iAIgIxuI+KQXyR3VzS/c4BGvo/bd6oV7yoZCiIJilXr0D3R32x0OZilDu6QHhtIDg6gSZUMlscgOS/OQ0dkNQ6fJ5gjf68y9/g5s+2EOBSkkVbLqqGJjsFbrRJ4Ie3OQu6GZ7QuP77zntdpziXh/T/d9l8Pe42A/YSjdC6sJovfs3fildsQcoInQetTm1OyizalrcKEdiIKWEdlED1LXiDZnbam2ObJpuobcUXK2h/BBEPORxUaTyfSIAW1ChDEUG1FyodiYE4NGjrxFyF5HR/eeL69DydywNbKhPkWuCj5xuBQtbluEJczZ2wKql4g7olg2F7/3cojwdSlKUtY180ReKzctiOCjP0AKLddpGQRRTl29i4eiRDZu64UltK7x7r7v7nR1YTGqyeNyU3QSFgkLNzRc0sXFRo0LUNbaRlTuioiNI7ujNIqNoftrxNVRGmm2+OoovTQroIDKEBTIFi6gIGqilJXlYfsWRPunLlxOvvnO/brGLjY9s/5IkO0BrkjWi9t4PxW4MEJGKViYOJHDSJZPgQkJSQra2DcCIg1uaApv/+t+2z0sFc9hIoNOKNYXQU8Wqw4xHGUoaPw+eigMp0GgNDRL8VhDvdaGNqfGVALaxCATUBDfZ7Q6agSECmeEqJboY41rhAo0sivYxOQnuAKqRETBFfLMI0tTqQ1KJrYDiCdzPxzp3rL9DvzR6JhN9Ulu8eWCG09i1mAWwII+NsRBkA99MfpDss+iEAoh/nW9GxvsW7bfOvaTJ9nLAg8RlAbxAD5GyFgC4nKbE0VwBX02wbBBKe6kNjT56KEgRqm0TpVzhYx6QdCz8YvcUbhQA7t6QbIgIuYtPCIbak8UQNvBL9AxvRER8xC9SO4o8lqJ0nBP2SArEAX5iYISHOqFCtD34rsoGyQdUWH6RofNjgBd9pwdiEa9COaE1n7zztCXe2GUbN/SCP3C9Vp9gPclCyHMdyetUeR9ubG+R5oXDFN+HhECAZVJEwQmo9oGE2aovd+5PIGc05XEoHDYTdFYEL9pKJtJ9qboJ2BIyG1Ov0K0ObBO2UAX2fCriYjmIgomfpFN7hoqDW2uySZ3Df5r2Obykhg3KTZ6DUKemB82tnE0PoiC2EadBsGVyRBhX2ZUUtANsdlMOMKDvi2QiULpoRx2HNt3wNKw4w766bVtQdZtbOCG2B4GZAEkeaKRRAoX9bpiGVRBUKuyE4kBEA7AKmJcLonAe16/4/pX39zHWMXOEWgDu8PJ9i2xdkYiIfI4C7YnNyamA1mDGFGZnKCANsUHIUNBr0xqJ+ZhYhtFNqFMCoOBRpnUxzbq7QqiNFmZJAqAKH65fiqFJQY4wGThckNEBT4wHnO37w58e8gLGWJLI8QIM9TOWkmW5MOaUoRCYwpTRORBfLoqIYKduOoau2tZaQ/e/k/XgcOeO+19dhsJNFjEkcbQJ+sIjEhUYcGNC8aVy13ojlJmQI2fSa/no2VEK4kGEW1uCAVN12iUSbZQTtiqyGJI5ioikuWLiGQxxCUouCEiuaOI6FcvsmcRkZxMZNAkxxVe0ZRG5jDKRnnEl9KLcfWSv4JeRAMRBVCApZIbKyOBoNsfwAIvmNsGopFMNJa7eSe+b3/7W/+6VdvYykTLBhdN8K9tTW7cluIypgYWHAFb4/gvEwJYhhAZi2obbrzz3zv7vutqvRX1+XPYECIeG0CdA0EP/1IvrzCrCfxM3LSIqPOQ8gOSCZ/Sln7xo4TpFjdKLu5nUn9XRM5GbS7eFdlEg+tLk9tcEMnCSznxWCKkWX0U05h9ECNutWG4JEZZVC8pGkWUSbJ6CTZLnhsYJYkbY6UXVgmysGk2tjBM4Wnrx9jCepIO80CHOXf1l6HvDkf+u8te/8Z9TPBABhgGSw1WJdFjo3nLdguGfm3jvca3Ot553/z9kejV1mwgkuvxsq2lMKNCduIcKE1sltgS52csdB0doJHB9fLg6JXJ4TSIh1UmR3BHjcEzSZ0nvk/2khUPaBulO0qvTBpOpSoULNydr4dCmqNh0OtBaSaonYFACrt5IPX15zo7Y7fvhH6+4m65mbxwKdp0NnzoB8e335kPfG/+8ajzws/x1pupLku2rT3aaYp2mSIIUR/iy5J8ft55lk6+udUgn9m4yZypjgZQKKIayMqkLAyJCYWgIGsQmj0thgtzNdQg9FCgrimIVyAXuGEgArm6ySlORJpHhFOciHK8Ar0oTxAJ9RIudjFl4KLSaM2uYTaii7AGkQ28l/n1/b5wNCKV5sVKYe6J703E+5Cw7gqV9fs80UggmQj3Jvx9KZQDaQZGTG8i2R+PD8VjuUBoCLvKeT2pUGggFsuwdxP4pXbkDASwhUgI78YTYXAgrxffEUJYYjyBPBTV4QEJSbSSCB1AJUXdNG2OC1DQZEP369ucfFQiXkHMI9Qg+i8VPYjSkA1yntzmIl6BQklGtTpKs6jeUGzU78hhKDaOGMVkqEHo2WyhBtFLhrxQJExRwi6XUywtQgAEG6lmB5YYZNVtZfhedB09PWbO2xl754FljCuh3QqnNhMs3GgMmmWgH8KJACLaneIKgBXiBGCI4kuF2AjxRWM4KbSTumlMj9YdNXxAGz71K9IezgehqdajeCZJY5G32hhDQNtwyqSGzUpzlkmd2mB+oE0kCmx2NkTMWpheh+Xl1E8Q6TB3YKZQVpfyLmUTCjPZ2bG+OKtAIUmWK7vDlVVy5ey2HpIG+NTGOh7b2PCNbbhxTP1SDgUm0IipDVBQovut+RVwpEyO6JmUZsCJ9EwO8Yt8iWR5QLWIiGrh5xERPUREYe4Q2VAt2Y5BFg+yRJG5g7KhX0VpuAcFmfFfIqL2lI3MHZQNfaw3MQ2pF71oc9jRykSBEo+hCSJ+C8YAfhaI8GzB7mR3mBPJyNAQM22hF2Hpsdl7sNM1tE0kbBaJPgYRCVyBv5gFuwUykK3H5UsPsjxIPU6vw+5CNhSC0hCJic2tHCjfzhYBsPrzL4WYRYY4cGOqGziwZGJKExH1xKNoTPx22cQkfqnoGgwJohhmI3cUiGh80eYiG5mY8EnWP1A02QzExuLWTUPDs2YjtyLrICin8EHgu/SG5yIahJ5RZfnqUuLGYmzht1nMXTRYBWNUNx8l6ZIttUgP9potHWQLolUobFMSH9bhYBYwB/wUG4F+6zObHrAlK5bOzFA/EVV+lt98VOVnbJMaDEeJn5mGExtFmxuKjcKhU7xr9KK6oQ9ixIC2/wcjP6B7AWaL0QAAAABJRU5ErkJggg== diff --git a/microsoft-teams/1.0.0/requirements.txt b/microsoft-teams/1.0.0/requirements.txt deleted file mode 100644 index fd7d3e06..00000000 --- a/microsoft-teams/1.0.0/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -requests==2.25.1 \ No newline at end of file diff --git a/microsoft-teams/1.0.0/src/__pycache__/teams.cpython-38.pyc b/microsoft-teams/1.0.0/src/__pycache__/teams.cpython-38.pyc deleted file mode 100644 index 1c3e43a22afe66da49e8f95ae63f31c72236a8d8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8115 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 diff --git a/microsoft-teams/1.0.0/src/app.py b/microsoft-teams/1.0.0/src/app.py deleted file mode 100644 index 0c072f89..00000000 --- a/microsoft-teams/1.0.0/src/app.py +++ /dev/null @@ -1,119 +0,0 @@ -import socket -import asyncio -import time -import random -import json -import teams #We have made changes to pymsteams module so please use teams.py DO NOT USE pymsteams.py - -from walkoff_app_sdk.app_base import AppBase - -class MsTeams(AppBase): - __version__ = "1.0.0" - app_name = "Microsoft Teams" # this needs to match "name" in api.yaml - - def __init__(self, redis, logger, console_logger=None): - """ - Each app should have this __init__ to set up Redis and logging. - :param redis: - :param logger: - :param console_logger: - """ - super().__init__(redis, logger, console_logger) - - # Write your data inside this function - def send_simple_text(self, webhook_url, message): - try: - myTeamsMessage = teams.connectorcard(str(webhook_url)) # You must create the connectorcard object with the Microsoft Webhook URL - myTeamsMessage.text(message) # Add text to the message. - myTeamsMessage.send()# send the message. - except Exception as e: - return f'{e.__class__} occured' - - return f'Message Sent' - - def send_rich_text(self, webhook_url, title, message, link_button_text, link_button_url): - try: - myTeamsMessage = teams.connectorcard(webhook_url) # You must create the connectorcard object with the Microsoft Webhook URL - myTeamsMessage.title(title) # title for your card - myTeamsMessage.text(message) # Add text to the message. - myTeamsMessage.addLinkButton(str(link_button_text), str(link_button_url)) # for button - myTeamsMessage.send()# send the message. - except Exception as e: - return f'{e.__class__} occured' - - return f'Message Sent' - - def send_actionable_msg(self, webhook_url, title, message, added_information, choices, callback_url): - try: - myTeamsMessage = teams.connectorcard(webhook_url) # You must create the connectorcard object with the Microsoft Webhook URL - myTeamsMessage.title(title) # title for your card - myTeamsMessage.text(message) # Add text to the message. - myTeamsPotentialAction3 = teams.potentialaction(_name = "Select_Action") - - if choices: - for choice in choices.split(","): - choice = choice.strip() - value = { - "choice": choice, - "extra": added_information, - } - - try: - choice_value = json.dumps(value) - except: - print("FAILED ENCODING {}".format(choice)) - choice_value = choice - - myTeamsPotentialAction3.choices.addChoices(choice, choice_value) #option 1 - - else: - value = { - "choice": "ACCEPT", - "extra": added_information, - } - - #print(f"VALUE: {value}") - - try: - accept = json.dumps(value) - except: - print("FAILED ENCODING ACCEPT") - accept = "ACCEPT" - - myTeamsPotentialAction3.choices.addChoices("Accept", accept) #option 1 - - value["choice"] = "REJECT" - try: - deny = json.dumps(value) - except: - print("FAILED ENCODING REJECT") - deny = "REJECT" - - myTeamsPotentialAction3.choices.addChoices("Reject", deny) #option 2 - - myTeamsPotentialAction3.addInput("MultichoiceInput","list","Select Action", False) #Dropdown menu - myTeamsPotentialAction3.addAction("HttpPost","Submit",callback_url) #post request to Shuffle - myTeamsMessage.addPotentialAction(myTeamsPotentialAction3) - myTeamsMessage.send()# send the message. - except Exception as e: - return f'{e} occured' - - return f'Message Sent' - - def get_user_input(self, webhook_url, title, message, callback_url): - try: - myTeamsMessage = teams.connectorcard(webhook_url) # You must create the connectorcard object with the Microsoft Webhook URL - myTeamsMessage.title(title) # Title for your card - myTeamsMessage.text(message) # Add text to the message. - myTeamsPotentialAction1 = teams.potentialaction(_name = "Comment") - myTeamsPotentialAction1.addInput("TextInput","comment", "Your text here..",False) - myTeamsPotentialAction1.addCommentAction("HttpPost","Submit", callback_url) - myTeamsMessage.addPotentialAction(myTeamsPotentialAction1) - myTeamsMessage.send() - except Exception as e: - return f'{e.__class__} occured' - - return f'Message Sent' - -if __name__ == "__main__": - MsTeams.run() diff --git a/microsoft-teams/1.0.0/src/teams.py b/microsoft-teams/1.0.0/src/teams.py deleted file mode 100644 index 31b69079..00000000 --- a/microsoft-teams/1.0.0/src/teams.py +++ /dev/null @@ -1,254 +0,0 @@ -#!/usr/bin/env python - -# reference: https://github.com/rveachkc/pymsteams/ -# reference: https://dev.outlook.com/connectors/reference - -import requests - -class TeamsWebhookException(Exception): - """custom exception for failed webhook call""" - pass - -class cardsection: - - def title(self, stitle): - # title of the section - self.payload["title"] = stitle - - def activityTitle(self, sactivityTitle): - # Title of the event or action. Often this will be the name of the "actor". - self.payload["activityTitle"] = sactivityTitle - - def activitySubtitle(self, sactivitySubtitle): - # A subtitle describing the event or action. Often this will be a summary of the action. - self.payload["activitySubtitle"] = sactivitySubtitle - - def activityImage(self, sactivityImage): - # URL to image or a data URI with the base64-encoded image inline. - # An image representing the action. Often this is an avatar of the "actor" of the activity. - self.payload["activityImage"] = sactivityImage - - def activityText(self, sactivityText): - # A full description of the action. - self.payload["activityText"] = sactivityText - - def addFact(self, factname, factvalue): - if "facts" not in self.payload.keys(): - self.payload["facts"] = [] - - newfact = { - "name" : factname, - "value" : factvalue - } - self.payload["facts"].append(newfact) - - def addImage(self, simage, ititle=None): - if "images" not in self.payload.keys(): - self.payload["images"] = [] - imobj = {} - imobj["image"] = simage - if ititle: - imobj["title"] = ititle - self.payload["images"].append(imobj) - - - def text(self, stext): - self.payload["text"] = stext - - def linkButton(self, buttontext, buttonurl): - self.payload["potentialAction"] = [ - { - "@context" : "http://schema.org", - "@type" : "ViewAction", - "name" : buttontext, - "target" : [ buttonurl ] - } - ] - - def disableMarkdown(self): - self.payload["markdown"] = False - - def enableMarkdown(self): - self.payload["markdown"] = True - - def dumpSection(self): - return self.payload - - def __init__(self): - self.payload = {} - - - -class potentialaction: - - def addInput(self,_type,_id,title, isMultiline = None): - if "inputs" not in self.payload.keys(): - self.payload["inputs"] = [] - if(self.choices.dumpChoices() == []): - input = { - "@type": _type, - "id": _id, - "isMultiline" :isMultiline, - "title": title - } - else: - input = { - "@type": _type, - "id": _id, - "isMultiline" :str(isMultiline).lower(), - "choices":self.choices.dumpChoices(), - "title": title - } - - self.payload["inputs"].append(input) - - def addAction(self,_type,_name,_target): - if "actions" not in self.payload.keys(): - self.payload["actions"] = [] - action = { - "@type": _type, - "name": _name, - "target": _target, - "body": "{{list.value}}" - } - self.payload["actions"].append(action) - - def addCommentAction(self,_type,_name,_target): - if "actions" not in self.payload.keys(): - self.payload["actions"] = [] - action = { - "@type": _type, - "name": _name, - "target": _target, - "body": "{{comment.value}}" - } - self.payload["actions"].append(action) - - def addOpenURI(self, _name, _targets): - """ - Creates a OpenURI action - - https://docs.microsoft.com/en-us/outlook/actionable-messages/message-card-reference#openuri-action - - :param _name: *Name of the text to appear inside the ActionCard* - :type _name: str - :param _targets: *A list of dictionaries, ex: `{"os": "default", "uri": "https://www..."}`* - :type _targets: list(dict()) - """ - self.payload["@type"] = "OpenUri" - self.payload["name"] = _name - if not isinstance(_targets, list): - raise TypeError("Target must be of type list(dict())") - self.payload["targets"] = _targets - - - def dumpPotentialAction(self): - return self.payload - - def __init__(self, _name, _type = "ActionCard"): - self.payload = {} - self.payload["@type"] = _type - self.payload["name"] = _name - self.choices = choice() - -class choice: - def __init__(self): - self.choices = [] - - def addChoices(self,_display,_value): - self.choices.append({ - "display": _display, - "value": _value - }) - def dumpChoices(self): - return self.choices - -class connectorcard: - - def text(self, mtext): - self.payload["text"] = mtext - - def title(self, mtitle): - self.payload["title"] = mtitle - - def summary(self, msummary): - self.payload["summary"] = msummary - - def color(self, mcolor): - if mcolor.lower() == "red": - self.payload["themeColor"] = "E81123" - else: - self.payload["themeColor"] = mcolor - - def addLinkButton(self, buttontext, buttonurl): - if "potentialAction" not in self.payload: - self.payload["potentialAction"] = [] - - thisbutton = { - "@context" : "http://schema.org", - "@type" : "ViewAction", - "name" : buttontext, - "target" : [ buttonurl ] - } - - self.payload["potentialAction"].append(thisbutton) - - def newhookurl(self, nhookurl): - self.hookurl = nhookurl - - def addSection(self, newsection): - # this function expects a cardsection object - if "sections" not in self.payload.keys(): - self.payload["sections"] = [] - - self.payload["sections"].append(newsection.dumpSection()) - - def addPotentialAction(self, newaction): - # this function expects a potential action object - if "potentialAction" not in self.payload.keys(): - self.payload["potentialAction"] = [] - - self.payload["potentialAction"].append(newaction.dumpPotentialAction()) - - def printme(self): - print("hookurl: %s" % self.hookurl) - print("payload: %s" % self.payload) - - def send(self): - headers = {"Content-Type":"application/json"} - r = requests.post( - self.hookurl, - json=self.payload, - headers=headers, - proxies=self.proxies, - timeout=self.http_timeout, - verify=self.verify, - ) - self.last_http_status = r - - if r.status_code == requests.codes.ok and r.text == '1': # pylint: disable=no-member - return True - else: - raise TeamsWebhookException(r.text) - - def __init__(self, hookurl, http_proxy=None, https_proxy=None, http_timeout=60, verify=None): - self.payload = {} - self.hookurl = hookurl - self.proxies = {} - self.http_timeout = http_timeout - self.verify = verify - self.last_http_response = None - - if http_proxy: - self.proxies['http'] = http_proxy - - if https_proxy: - self.proxies['https'] = https_proxy - - if not self.proxies: - self.proxies = None - - -def formaturl(display, url): - mdurl = "[%s](%s)" % (display, url) - return mdurl From 0a2dee4ff81827ca5b34954c87b33ad2fd13f4a4 Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 4 Sep 2024 15:51:31 +0200 Subject: [PATCH 134/259] Moved a lot of old APIs to be unspported as they should be managed as OpenAPI apps with the new Oauth2 systems --- .../crowdstrike-falcon/1.0.0/Dockerfile | 26 + unsupported/crowdstrike-falcon/1.0.0/api.yaml | 17996 ++++++++++++++++ .../crowdstrike-falcon/1.0.0/requirements.txt | 1 + .../crowdstrike-falcon/1.0.0/src/app.py | 3749 ++++ .../cylance}/1.0.0/Dockerfile | 0 .../cylance}/1.0.0/api.yaml | 0 .../cylance}/1.0.0/requirements.txt | 0 .../cylance}/1.0.0/src/app.py | 0 .../hoxhunt}/1.0.0/Dockerfile | 0 .../hoxhunt}/1.0.0/api.yaml | 0 .../hoxhunt}/1.0.0/requirements.txt | 0 .../hoxhunt}/1.0.0/src/app.py | 0 .../1.0.0/Dockerfile | 0 .../1.0.0/README.md | 0 .../1.0.0/api.yaml | 0 .../1.0.0/requirements.txt | 0 .../1.0.0/src/app.py | 0 .../microsoft-intune}/1.0.0/Dockerfile | 0 .../microsoft-intune}/1.0.0/README.md | 0 .../microsoft-intune}/1.0.0/api.yaml | 0 .../microsoft-intune}/1.0.0/requirements.txt | 0 .../microsoft-intune}/1.0.0/src/app.py | 0 .../1.0.0/Dockerfile | 0 .../1.0.0/README.md | 0 .../1.0.0/api.yaml | 0 .../1.0.0/requirements.txt | 0 .../1.0.0/src/app.py | 0 .../1.0.0/Dockerfile | 0 .../microsoft-security-oauth2}/1.0.0/api.yaml | 0 .../1.0.0/requirements.txt | 0 .../1.0.0/src/app.py | 0 .../1.0.0/Dockerfile | 0 .../1.0.0/README.md | 33 + .../1.0.0/api.yaml | 190 + .../1.0.0/requirements.txt | 0 .../1.0.0/src/app.py | 275 + .../microsoft-teams}/1.0.0/Dockerfile | 0 .../1.0.0/MicrosoftTeams-image.png | Bin 0 -> 104418 bytes unsupported/microsoft-teams/1.0.0/README.md | 30 + unsupported/microsoft-teams/1.0.0/api.yaml | 165 + .../microsoft-teams}/1.0.0/requirements.txt | 0 unsupported/microsoft-teams/1.0.0/src/app.py | 119 + .../microsoft-teams/1.0.0/src/teams.py | 254 + .../passivetotal}/1.0.0/Dockerfile | 0 .../passivetotal}/1.0.0/api.yaml | 0 .../passivetotal/1.0.0/requirements.txt | 1 + .../passivetotal}/1.0.0/src/app.py | 0 .../recordedfuture}/1.0.0/Dockerfile | 0 .../recordedfuture}/1.0.0/api.yaml | 0 .../recordedfuture/1.0.0/requirements.txt | 1 + .../recordedfuture}/1.0.0/src/app.py | 0 unsupported/servicenow/1.0.0/Dockerfile | 26 + unsupported/servicenow/1.0.0/api.yaml | 146 + unsupported/servicenow/1.0.0/requirements.txt | 1 + unsupported/servicenow/1.0.0/src/app.py | 204 + .../splunk}/1.0.0/Dockerfile | 0 unsupported/splunk/1.0.0/api.yaml | 62 + unsupported/splunk/1.0.0/docker-compose.yml | 14 + unsupported/splunk/1.0.0/env.txt | 4 + unsupported/splunk/1.0.0/requirements.txt | 2 + unsupported/splunk/1.0.0/src/app.py | 124 + unsupported/testing/1.0.0/Dockerfile | 26 + unsupported/testing/1.0.0/api.yaml | 178 + unsupported/testing/1.0.0/requirements.txt | 1 + unsupported/testing/1.0.0/run | 17 + unsupported/testing/1.0.0/src/app.py | 101 + unsupported/testing/1.0.0/tmp.py | 128 + .../thehive/1.0.0}/Dockerfile | 0 .../thehive}/1.0.0/api.yaml | 0 .../thehive}/1.0.0/docker-compose.yml | 0 .../thehive}/1.0.0/env.txt | 0 .../thehive}/1.0.0/requirements.txt | 0 {thehive => unsupported/thehive}/1.0.0/run | 0 .../thehive}/1.0.0/src/app.py | 0 .../thehive/1.1.0}/Dockerfile | 0 .../thehive}/1.1.0/api.yaml | 0 .../thehive}/1.1.0/docker-compose.yml | 0 .../thehive}/1.1.0/env.txt | 0 .../thehive}/1.1.0/requirements.txt | 0 {thehive => unsupported/thehive}/1.1.0/run | 0 .../thehive}/1.1.0/src/app.py | 0 .../thehive/1.1.1}/Dockerfile | 0 .../thehive}/1.1.1/api.yaml | 0 .../thehive}/1.1.1/docker-compose.yml | 0 .../thehive}/1.1.1/env.txt | 0 .../thehive}/1.1.1/requirements.txt | 0 {thehive => unsupported/thehive}/1.1.1/run | 0 .../thehive}/1.1.1/src/app.py | 0 .../thehive/1.1.2}/Dockerfile | 0 .../thehive}/1.1.2/api.yaml | 0 .../thehive}/1.1.2/docker-compose.yml | 0 .../thehive}/1.1.2/env.txt | 0 .../thehive}/1.1.2/requirements.txt | 0 {thehive => unsupported/thehive}/1.1.2/run | 0 .../thehive}/1.1.2/src/app.py | 0 unsupported/thehive/1.1.3/Dockerfile | 26 + .../thehive}/1.1.3/api.yaml | 0 .../thehive}/1.1.3/docker-compose.yml | 0 .../thehive}/1.1.3/env.txt | 0 .../thehive}/1.1.3/requirements.txt | 0 {thehive => unsupported/thehive}/1.1.3/run | 0 .../thehive}/1.1.3/src/app.py | 0 {thehive => unsupported/thehive}/README.md | 0 {thehive => unsupported/thehive}/conf.png | Bin unsupported/twitter/1.0.0/Dockerfile | 26 + .../twitter}/1.0.0/api.yaml | 0 .../twitter}/1.0.0/requirements.txt | 0 .../twitter}/1.0.0/src/app.py | 0 unsupported/vulndb/1.0.0/Dockerfile | 26 + {vulndb => unsupported/vulndb}/1.0.0/api.yaml | 0 {vulndb => unsupported/vulndb}/1.0.0/docs.md | 0 .../vulndb}/1.0.0/requirements.txt | 0 .../vulndb}/1.0.0/shield-vulndb.svg | 0 .../vulndb}/1.0.0/src/app.py | 0 114 files changed, 23952 insertions(+) create mode 100644 unsupported/crowdstrike-falcon/1.0.0/Dockerfile create mode 100755 unsupported/crowdstrike-falcon/1.0.0/api.yaml create mode 100644 unsupported/crowdstrike-falcon/1.0.0/requirements.txt create mode 100755 unsupported/crowdstrike-falcon/1.0.0/src/app.py rename {cylance => unsupported/cylance}/1.0.0/Dockerfile (100%) rename {cylance => unsupported/cylance}/1.0.0/api.yaml (100%) rename {cylance => unsupported/cylance}/1.0.0/requirements.txt (100%) rename {cylance => unsupported/cylance}/1.0.0/src/app.py (100%) rename {hoxhunt => unsupported/hoxhunt}/1.0.0/Dockerfile (100%) rename {hoxhunt => unsupported/hoxhunt}/1.0.0/api.yaml (100%) rename {hoxhunt => unsupported/hoxhunt}/1.0.0/requirements.txt (100%) rename {hoxhunt => unsupported/hoxhunt}/1.0.0/src/app.py (100%) rename {microsoft-identity-and-access => unsupported/microsoft-identity-and-access}/1.0.0/Dockerfile (100%) rename {microsoft-identity-and-access => unsupported/microsoft-identity-and-access}/1.0.0/README.md (100%) rename {microsoft-identity-and-access => unsupported/microsoft-identity-and-access}/1.0.0/api.yaml (100%) rename {microsoft-identity-and-access => unsupported/microsoft-identity-and-access}/1.0.0/requirements.txt (100%) rename {microsoft-identity-and-access => unsupported/microsoft-identity-and-access}/1.0.0/src/app.py (100%) rename {microsoft-intune => unsupported/microsoft-intune}/1.0.0/Dockerfile (100%) rename {microsoft-intune => unsupported/microsoft-intune}/1.0.0/README.md (100%) rename {microsoft-intune => unsupported/microsoft-intune}/1.0.0/api.yaml (100%) rename {microsoft-intune => unsupported/microsoft-intune}/1.0.0/requirements.txt (100%) rename {microsoft-intune => unsupported/microsoft-intune}/1.0.0/src/app.py (100%) rename {microsoft-security-and-compliance => unsupported/microsoft-security-and-compliance}/1.0.0/Dockerfile (100%) rename {microsoft-security-and-compliance => unsupported/microsoft-security-and-compliance}/1.0.0/README.md (100%) rename {microsoft-security-and-compliance => unsupported/microsoft-security-and-compliance}/1.0.0/api.yaml (100%) rename {microsoft-security-and-compliance => unsupported/microsoft-security-and-compliance}/1.0.0/requirements.txt (100%) rename {microsoft-security-and-compliance => unsupported/microsoft-security-and-compliance}/1.0.0/src/app.py (100%) rename {microsoft-security-oauth2 => unsupported/microsoft-security-oauth2}/1.0.0/Dockerfile (100%) rename {microsoft-security-oauth2 => unsupported/microsoft-security-oauth2}/1.0.0/api.yaml (100%) rename {microsoft-security-oauth2 => unsupported/microsoft-security-oauth2}/1.0.0/requirements.txt (100%) rename {microsoft-security-oauth2 => unsupported/microsoft-security-oauth2}/1.0.0/src/app.py (100%) rename {passivetotal => unsupported/microsoft-teams-system-access}/1.0.0/Dockerfile (100%) create mode 100644 unsupported/microsoft-teams-system-access/1.0.0/README.md create mode 100644 unsupported/microsoft-teams-system-access/1.0.0/api.yaml rename {passivetotal => unsupported/microsoft-teams-system-access}/1.0.0/requirements.txt (100%) create mode 100644 unsupported/microsoft-teams-system-access/1.0.0/src/app.py rename {recordedfuture => unsupported/microsoft-teams}/1.0.0/Dockerfile (100%) create mode 100644 unsupported/microsoft-teams/1.0.0/MicrosoftTeams-image.png create mode 100644 unsupported/microsoft-teams/1.0.0/README.md create mode 100644 unsupported/microsoft-teams/1.0.0/api.yaml rename {recordedfuture => unsupported/microsoft-teams}/1.0.0/requirements.txt (100%) create mode 100644 unsupported/microsoft-teams/1.0.0/src/app.py create mode 100644 unsupported/microsoft-teams/1.0.0/src/teams.py rename {twitter => unsupported/passivetotal}/1.0.0/Dockerfile (100%) rename {passivetotal => unsupported/passivetotal}/1.0.0/api.yaml (100%) create mode 100644 unsupported/passivetotal/1.0.0/requirements.txt rename {passivetotal => unsupported/passivetotal}/1.0.0/src/app.py (100%) rename {vulndb => unsupported/recordedfuture}/1.0.0/Dockerfile (100%) rename {recordedfuture => unsupported/recordedfuture}/1.0.0/api.yaml (100%) create mode 100644 unsupported/recordedfuture/1.0.0/requirements.txt rename {recordedfuture => unsupported/recordedfuture}/1.0.0/src/app.py (100%) create mode 100644 unsupported/servicenow/1.0.0/Dockerfile create mode 100644 unsupported/servicenow/1.0.0/api.yaml create mode 100644 unsupported/servicenow/1.0.0/requirements.txt create mode 100755 unsupported/servicenow/1.0.0/src/app.py rename {thehive => unsupported/splunk}/1.0.0/Dockerfile (100%) create mode 100644 unsupported/splunk/1.0.0/api.yaml create mode 100644 unsupported/splunk/1.0.0/docker-compose.yml create mode 100644 unsupported/splunk/1.0.0/env.txt create mode 100644 unsupported/splunk/1.0.0/requirements.txt create mode 100644 unsupported/splunk/1.0.0/src/app.py create mode 100644 unsupported/testing/1.0.0/Dockerfile create mode 100644 unsupported/testing/1.0.0/api.yaml create mode 100644 unsupported/testing/1.0.0/requirements.txt create mode 100755 unsupported/testing/1.0.0/run create mode 100644 unsupported/testing/1.0.0/src/app.py create mode 100644 unsupported/testing/1.0.0/tmp.py rename {thehive/1.1.0 => unsupported/thehive/1.0.0}/Dockerfile (100%) rename {thehive => unsupported/thehive}/1.0.0/api.yaml (100%) rename {thehive => unsupported/thehive}/1.0.0/docker-compose.yml (100%) rename {thehive => unsupported/thehive}/1.0.0/env.txt (100%) rename {thehive => unsupported/thehive}/1.0.0/requirements.txt (100%) rename {thehive => unsupported/thehive}/1.0.0/run (100%) rename {thehive => unsupported/thehive}/1.0.0/src/app.py (100%) rename {thehive/1.1.1 => unsupported/thehive/1.1.0}/Dockerfile (100%) rename {thehive => unsupported/thehive}/1.1.0/api.yaml (100%) rename {thehive => unsupported/thehive}/1.1.0/docker-compose.yml (100%) rename {thehive => unsupported/thehive}/1.1.0/env.txt (100%) rename {thehive => unsupported/thehive}/1.1.0/requirements.txt (100%) rename {thehive => unsupported/thehive}/1.1.0/run (100%) rename {thehive => unsupported/thehive}/1.1.0/src/app.py (100%) rename {thehive/1.1.2 => unsupported/thehive/1.1.1}/Dockerfile (100%) rename {thehive => unsupported/thehive}/1.1.1/api.yaml (100%) rename {thehive => unsupported/thehive}/1.1.1/docker-compose.yml (100%) rename {thehive => unsupported/thehive}/1.1.1/env.txt (100%) rename {thehive => unsupported/thehive}/1.1.1/requirements.txt (100%) rename {thehive => unsupported/thehive}/1.1.1/run (100%) rename {thehive => unsupported/thehive}/1.1.1/src/app.py (100%) rename {thehive/1.1.3 => unsupported/thehive/1.1.2}/Dockerfile (100%) rename {thehive => unsupported/thehive}/1.1.2/api.yaml (100%) rename {thehive => unsupported/thehive}/1.1.2/docker-compose.yml (100%) rename {thehive => unsupported/thehive}/1.1.2/env.txt (100%) rename {thehive => unsupported/thehive}/1.1.2/requirements.txt (100%) rename {thehive => unsupported/thehive}/1.1.2/run (100%) rename {thehive => unsupported/thehive}/1.1.2/src/app.py (100%) create mode 100644 unsupported/thehive/1.1.3/Dockerfile rename {thehive => unsupported/thehive}/1.1.3/api.yaml (100%) rename {thehive => unsupported/thehive}/1.1.3/docker-compose.yml (100%) rename {thehive => unsupported/thehive}/1.1.3/env.txt (100%) rename {thehive => unsupported/thehive}/1.1.3/requirements.txt (100%) rename {thehive => unsupported/thehive}/1.1.3/run (100%) rename {thehive => unsupported/thehive}/1.1.3/src/app.py (100%) rename {thehive => unsupported/thehive}/README.md (100%) rename {thehive => unsupported/thehive}/conf.png (100%) create mode 100644 unsupported/twitter/1.0.0/Dockerfile rename {twitter => unsupported/twitter}/1.0.0/api.yaml (100%) rename {twitter => unsupported/twitter}/1.0.0/requirements.txt (100%) rename {twitter => unsupported/twitter}/1.0.0/src/app.py (100%) create mode 100644 unsupported/vulndb/1.0.0/Dockerfile rename {vulndb => unsupported/vulndb}/1.0.0/api.yaml (100%) rename {vulndb => unsupported/vulndb}/1.0.0/docs.md (100%) rename {vulndb => unsupported/vulndb}/1.0.0/requirements.txt (100%) rename {vulndb => unsupported/vulndb}/1.0.0/shield-vulndb.svg (100%) rename {vulndb => unsupported/vulndb}/1.0.0/src/app.py (100%) diff --git a/unsupported/crowdstrike-falcon/1.0.0/Dockerfile b/unsupported/crowdstrike-falcon/1.0.0/Dockerfile new file mode 100644 index 00000000..740fee62 --- /dev/null +++ b/unsupported/crowdstrike-falcon/1.0.0/Dockerfile @@ -0,0 +1,26 @@ +# Base our app image off of the WALKOFF App SDK image +FROM frikky/shuffle:app_sdk as base + +# We're going to stage away all of the bloat from the build tools so lets create a builder stage +FROM base as builder + +# Install all alpine build tools needed for our pip installs +RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev + +# Install all of our pip packages in a single directory that we can copy to our base image later +RUN mkdir /install +WORKDIR /install +COPY requirements.txt /requirements.txt +RUN pip install --prefix="/install" -r /requirements.txt + +# Switch back to our base image and copy in all of our built packages and source code +FROM base +COPY --from=builder /install /usr/local +COPY src /app + +# Install any binary dependencies needed in our final image - this can be a lot of different stuff +RUN apk --no-cache add --update libmagic + +# Finally, lets run our app! +WORKDIR /app +CMD python app.py --log-level DEBUG diff --git a/unsupported/crowdstrike-falcon/1.0.0/api.yaml b/unsupported/crowdstrike-falcon/1.0.0/api.yaml new file mode 100755 index 00000000..c6738a17 --- /dev/null +++ b/unsupported/crowdstrike-falcon/1.0.0/api.yaml @@ -0,0 +1,17996 @@ +name: Crowdstrike Falcon +is_valid: true +id: "" +link: https://api.crowdstrike.com +app_version: 1.0.0 +sharing_config: "" +generated: true +downloaded: false +sharing: false +verified: false +invalid: false +activated: true +tested: false +hash: "" +private_id: "" +description: Each API endpoint requires authorization via an OAuth2 token. Your first API request + should retrieve an OAuth2 token using the `oauth2/token` endpoint, such as `https://api.crowdstrike.com/oauth2/token`. Any action should be preceeded by a `get oauth2 access token` action titled `auth` that feeds the access token into it. Tokens expire after 30 minutes, after which you should make a new token request + to continue making API requests. +environment: Shuffle +contact_info: + name: "test" + url: "test" +referenceinfo: + documentationurl: "" + githuburl: "" +foldermount: + foldermount: false + sourcefolder: "" + destinationfolder: "" +actions: +- description: "" + name: generate_oauth2_access_token + label: OAuth2 - Generate an OAuth2 access token + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Accept: application/json + Content-Type: application/x-www-form-urlencoded + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_detect_aggregates + label: Detects - Get detect aggregates + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: view_information_about_detections + label: Detects - View information about detections + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Generated by shuffler.io OpenAPI + name: body + example: |- + { + "ids": "${ids}" + } + value: |- + { + "ids": "${ids}" + } + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: modify_detections + label: Detects - Modify the state assignee and visibility of detections + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Generated by shuffler.io OpenAPI + name: body + example: |- + { + "assigned_to_uuid": "${assigned_to_uuid}", + "comment": "${comment}", + "ids": "${ids}", + "show_in_ui": "${show_in_ui}", + "status": "${status}" + } + value: |- + { + "assigned_to_uuid": "${assigned_to_uuid}", + "comment": "${comment}", + "ids": "${ids}", + "show_in_ui": "${show_in_ui}", + "status": "${status}" + } + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: search_for_detection_ids + label: Detects - Search for detection IDs that match a given query + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The first detection to return, where `0` is the latest detection. + Use with the `limit` parameter to manage pagination of results. + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: 'The maximum number of detections to return in this response (default: + 9999; max: 9999). Use with the `offset` parameter to manage pagination of results.' + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: |- + Sort detections using these options: + + - `first_behavior`: Timestamp of the first behavior associated with this detection + - `last_behavior`: Timestamp of the last behavior associated with this detection + - `max_severity`: Highest severity of the behaviors associated with this detection + - `max_confidence`: Highest confidence of the behaviors associated with this detection + - `adversary_id`: ID of the adversary associated with this detection, if any + - `devices.hostname`: Hostname of the host where this detection was detected + + Sort either `asc` (ascending) or `desc` (descending). For example: `last_behavior|asc` + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: "Filter detections using a query in Falcon Query Language (FQL) An + asterisk wildcard `*` includes all results. \n\nCommon filter options include:\n\n- + `status`\n- `device.device_id`\n- `max_severity`\n\nThe full list of valid filter + options is extensive. Review it in our [documentation inside the Falcon console](https://falcon.crowdstrike.com/support/documentation/2/query-api-reference#detections_fql)." + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Search all detection metadata for the provided string + name: q + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: retrieve_set_of_host_groups + label: Host Group - Retrieve a set of Host Groups by specifying their IDs + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The IDs of the Host Groups to return + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: delete_set_of_host_groups + label: Host Group - Delete a set of Host Groups by specifying their IDs + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The IDs of the Host Groups to delete + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: create_host_groups + label: Host Group - Create Host Groups by specifying details about the group to create + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: update_host_groups + label: Host Group - Update Host Groups by specifying the ID of the group and details to update + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: search_for_host_groups + label: Host Group - Search for Host Groups in your environment by providing an FQL filter and + paging details Returns a set of Host Groups which match the filter criteria + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The filter expression that should be used to limit the results + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The offset to start retrieving records from + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The maximum records to return. [1-5000] + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The property to sort by + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: search_for_host_group_ids + label: Host Group - Search for Host Groups in your environment by providing an FQL filter and + paging details Returns a set of Host Group IDs which match the filter criteria + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The filter expression that should be used to limit the results + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The offset to start retrieving records from + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The maximum records to return. [1-5000] + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The property to sort by + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: search_for_host_group_members + label: Host Group - Search for members of a Host Group in your environment by providing an FQL + filter and paging details Returns a set of host details which match the filter + criteria + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The ID of the Host Group to search for members of + name: id + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The filter expression that should be used to limit the results + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The offset to start retrieving records from + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The maximum records to return. [1-5000] + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The property to sort by + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: perform_action_on_host_group + label: Host Group - Perform the specified action on the Host Groups specified in the request + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The action to perform + name: action_name + example: "" + multiline: false + options: + - add-hosts + - remove-hosts + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The ID of the host group to change + name: host_group_id + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The hostnames to change + name: hostnames + example: "" + multiline: true + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: search_for_host_group_member_ids + label: Host Group - Search for members of a Host Group in your environment by providing an FQL + filter and paging details Returns a set of Agent IDs which match the filter criteria + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The ID of the Host Group to search for members of + name: id + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The filter expression that should be used to limit the results + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The offset to start retrieving records from + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The maximum records to return. [1-5000] + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The property to sort by + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: retrieve_hidden_hosts + label: Hosts - Retrieve hidden hosts that match the provided filter criteria + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The offset to start retrieving records from + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The maximum records to return. [1-5000] + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The property to sort by (e.g. status.desc or hostname.asc) + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The filter expression that should be used to limit the results + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: search_for_hosts + label: Hosts - Search for hosts in your environment by platform hostname IP and other criteria + with continuous pagination capability based on offset pointer which expires after + 2 minutes with no maximum limit + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The offset to page from, for the next result set + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The maximum records to return. [1-5000] + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The property to sort by (e.g. status.desc or hostname.asc) + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The filter expression that should be used to limit the results + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: modify_host_tags + label: Hosts - Append or remove one or more Falcon Grouping Tags on one or more hosts + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Generated by shuffler.io OpenAPI + name: body + example: |- + { + "action": "${action}", + "device_ids": "${device_ids}", + "tags": "${tags}" + } + value: |- + { + "action": "${action}", + "device_ids": "${device_ids}", + "tags": "${tags}" + } + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_details_on_hosts + label: Hosts - Get details on one or more hosts by providing agent IDs AID You can get a + hosts agent IDs AIDs from the devicesqueriesdevicesv1 endpoint the Falcon console + or the Streaming API + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The host agentIDs used to get details on + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: take_action_on_hosts + label: Hosts - Take various actions on the hosts in your environment Contain or lift containment + on a host Delete or restore a host + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: |- + Specify one of these actions: + + - `contain` - This action contains the host, which stops any network communications to locations other than the CrowdStrike cloud and IPs specified in your [containment policy](https://falcon.crowdstrike.com/support/documentation/11/getting-started-guide#containmentpolicy) + - `lift_containment`: This action lifts containment on the host, which returns its network communications to normal + - `hide_host`: This action will delete a host. After the host is deleted, no new detections for that host will be reported via UI or APIs + - `unhide_host`: This action will restore a host. Detection reporting will resume after the host is restored + name: action_name + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Generated by shuffler.io OpenAPI + name: body + example: |- + { + "action_parameters": "${action_parameters}", + "ids": "${ids}" + } + value: |- + { + "action_parameters": "${action_parameters}", + "ids": "${ids}" + } + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: search_for_hosts + label: Hosts - Search for hosts in your environment by platform hostname IP and other criteria + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The offset to start retrieving records from + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The maximum records to return. [1-5000] + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The property to sort by (e.g. status.desc or hostname.asc) + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The filter expression that should be used to limit the results + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: download_analysis_artifacts + label: FalconX Sandbox - Download IOC packs PCAP files and other analysis artifacts + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: ID of an artifact, such as an IOC pack, PCAP file, or actor image. + Find an artifact ID in a report or summary. + name: id + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: gzip + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The name given to your downloaded file. + name: name + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_sandbox_reports + label: FalconX Sandbox - Find sandbox reports by providing an FQL filter and paging details Returns + a set of report IDs that match your criteria + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Optional filter and sort criteria in the form of an FQL query. For + more information about FQL queries, see [our FQL documentation in Falcon](https://falcon.crowdstrike.com/support/documentation/45/falcon-query-language-feature-guide). + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The offset to start retrieving reports from. + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: 'Maximum number of report IDs to return. Max: 5000.' + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: 'Sort order: `asc` or `desc`.' + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_a_full_sandbox_report + label: FalconX Sandbox - Get a full sandbox report + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: ID of a report. Find a report ID from the response when submitting + a malware sample or search with `/falconx/queries/reports/v1`. + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: delete_report + label: FalconX Sandbox - Delete report based on the report ID Operation can be checked for success + by polling for the report ID on the reportsummaries endpoint + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: ID of a report. + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: retrieve_list_of_samples + label: FalconX Sandbox - retrieve a list with sha256 of samples that exist and customer has rights + to access them maximum number of accepted items is 200 + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Generated by shuffler.io OpenAPI + name: body + example: |- + { + "sha256s": "${sha256s}" + } + value: |- + { + "sha256s": "${sha256s}" + } + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: check_status_of_sandbox_analysis + label: FalconX Sandbox - Check the status of a sandbox analysis Time required for analysis varies + but is usually less than 15 minutes + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: ID of a submitted malware sample. Find a submission ID from the response + when submitting a malware sample or search with `/falconx/queries/submissions/v1`. + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: submit_upload_for_sandbox_analysis + label: FalconX Sandbox - Submit an uploaded file or a URL for sandbox analysis Time required for analysis + varies but is usually less than 15 minutes + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_a_short_summary_version_of_a_sandbox_report + label: FalconX Sandbox - Get a short summary version of a sandbox report + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: ID of a summary. Find a summary ID from the response when submitting + a malware sample or search with `/falconx/queries/reports/v1`. + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: find_submission_ids_for_uploaded_files + label: FalconX Sandbox - Find submission IDs for uploaded files by providing an FQL filter and paging + details Returns a set of submission IDs that match your criteria + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Optional filter and sort criteria in the form of an FQL query. For + more information about FQL queries, see [our FQL documentation in Falcon](https://falcon.crowdstrike.com/support/documentation/45/falcon-query-language-feature-guide). + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The offset to start retrieving submissions from. + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: 'Maximum number of submission IDs to return. Max: 5000.' + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: 'Sort order: `asc` or `desc`.' + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: retrieve_the_file_associated_with_the_given_id_sha256 + label: FalconX Sandbox - retrieve the file associated with the given ID SHA256 + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The file SHA256. + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Flag whether the sample should be zipped and password protected with + pass='infected' + name: password_protected + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: delete_sample_from_the_collection + label: FalconX Sandbox - Removes a sample including file meta and submissions from the collection + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The file SHA256. + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: upload_for_sandbox_analysis + label: FalconX Sandbox - Upload a file for sandbox analysis After uploading use falconxentitiessubmissionsv1 + to start analyzing the file + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Name of the file. + name: file_name + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: A descriptive comment to identify the file for other users. + name: comment + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: "Defines visibility of this file in Falcon MalQuery, either via the + API or the Falcon console.\n\n- `true`: File is only shown to users within your + customer account\n- `false`: File can be seen by other CrowdStrike customers + \n\nDefault: `true`." + name: is_confidential + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: search_for_behaviors + label: Incidents - Search for behaviors by providing an FQL filter sorting and paging details + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Optional filter and sort criteria in the form of an FQL query. For + more information about FQL queries, see [our FQL documentation in Falcon](https://falcon.crowdstrike.com/support/documentation/45/falcon-query-language-feature-guide). + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Starting index of overall result set from which to return ids. + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The maximum records to return. [1-500] + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The property to sort on, followed by a dot (.), followed by the sort + direction, either "asc" or "desc". + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: search_for_incidents + label: Incidents - Search for incidents by providing an FQL filter sorting and paging details + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The property to sort on, followed by a dot (.), followed by the sort + direction, either "asc" or "desc". + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Optional filter and sort criteria in the form of an FQL query. For + more information about FQL queries, see [our FQL documentation in Falcon](https://falcon.crowdstrike.com/support/documentation/45/falcon-query-language-feature-guide). + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Starting index of overall result set from which to return ids. + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The maximum records to return. [1-500] + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: query_crowdscore + label: Incidents - Query environment wide CrowdScore and return the entity data + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Optional filter and sort criteria in the form of an FQL query. For + more information about FQL queries, see [our FQL documentation in Falcon](https://falcon.crowdstrike.com/support/documentation/45/falcon-query-language-feature-guide). + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Starting index of overall result set from which to return ids. + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The maximum records to return. [1-2500] + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The property to sort on, followed by a dot (.), followed by the sort + direction, either "asc" or "desc". + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: perform_actions_on_incidents + label: Incidents - Perform a set of actions on one or more incidents such as adding tags or + comments or updating the incident name or description + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Generated by shuffler.io OpenAPI + name: body + example: |- + { + "action_parameters": "${action_parameters}", + "ids": "${ids}" + } + value: |- + { + "action_parameters": "${action_parameters}", + "ids": "${ids}" + } + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_details_on_behaviors + label: Incidents - Get details on behaviors by providing behavior IDs + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Generated by shuffler.io OpenAPI + name: body + example: |- + { + "ids": "${ids}" + } + value: |- + { + "ids": "${ids}" + } + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_details_on_incidents + label: Incidents - Get details on incidents by providing incident IDs + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Generated by shuffler.io OpenAPI + name: body + example: |- + { + "ids": "${ids}" + } + value: |- + { + "ids": "${ids}" + } + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_combined_for_indicators + label: IOCs - Get Combined for Indicators + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The filter expression that should be used to limit the results. + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The offset to start retrieving records from. Offset and After params + are mutually exclusive. If none provided then scrolling will be used by default. + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The maximum records to return. + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The sort expression that should be used to sort the results. + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_indicators_by_ids + label: IOCs - Get Indicators by ids + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The ids of the Indicators to retrieve + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: delete_indicators_by_ids + label: IOCs - Delete Indicators by ids + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The FQL expression to delete Indicators in bulk. If both 'filter' + and 'ids' are provided, then filter takes precedence and ignores ids. + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The ids of the Indicators to delete. If both 'filter' and 'ids' are + provided, then filter takes precedence and ignores ids + name: ids + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The comment why these indicators were deleted + name: comment + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: create_indicators + label: IOCs - Create Indicators + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Whether to submit to retrodetects + name: retrodetects + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Set to true to ignore warnings and add all IOCs + name: ignore_warnings + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Generated by shuffler.io OpenAPI + name: body + example: |- + { + "comment": "${comment}", + "indicators": "${indicators}" + } + value: |- + { + "comment": "${comment}", + "indicators": "${indicators}" + } + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: update_indicators + label: IOCs - Update Indicators + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Whether to submit to retrodetects + name: retrodetects + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Set to true to ignore warnings and add all IOCs + name: ignore_warnings + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Generated by shuffler.io OpenAPI + name: body + example: |- + { + "bulk_update": "${bulk_update}", + "comment": "${comment}", + "indicators": "${indicators}" + } + value: |- + { + "bulk_update": "${bulk_update}", + "comment": "${comment}", + "indicators": "${indicators}" + } + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_number_of_hosts_that_have_observed_a_given_custom_ioc + label: IOCs - Number of hosts in your customer account that have observed a given custom + IOC + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: |2 + + The type of the indicator. Valid types include: + + sha256: A hex-encoded sha256 hash string. Length - min: 64, max: 64. + + md5: A hex-encoded md5 hash string. Length - min 32, max: 32. + + domain: A domain name. Length - min: 1, max: 200. + + ipv4: An IPv4 address. Must be a valid IP address. + + ipv6: An IPv6 address. Must be a valid IP address. + name: type + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The string representation of the indicator + name: value + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_process_details + label: IOCs - For the provided ProcessID retrieve the process details + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: ProcessID for the running process you want to lookup + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_hosts_that_have_observed_a_given_custom_ioc + label: IOCs - Find hosts that have observed a given custom IOC For details about those + hosts use GET devicesentitiesdevicesv1 + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: |2 + + The type of the indicator. Valid types include: + + sha256: A hex-encoded sha256 hash string. Length - min: 64, max: 64. + + md5: A hex-encoded md5 hash string. Length - min 32, max: 32. + + domain: A domain name. Length - min: 1, max: 200. + + ipv4: An IPv4 address. Must be a valid IP address. + + ipv6: An IPv6 address. Must be a valid IP address. + name: type + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The string representation of the indicator + name: value + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The first process to return, where 0 is the latest offset. Use with + the offset parameter to manage pagination of results. + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The first process to return, where 0 is the latest offset. Use with + the limit parameter to manage pagination of results. + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: search_for_processes_associated_with_a_custom_ioc + label: IOCs - Search for processes associated with a custom IOC + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: |2 + + The type of the indicator. Valid types include: + + sha256: A hex-encoded sha256 hash string. Length - min: 64, max: 64. + + md5: A hex-encoded md5 hash string. Length - min 32, max: 32. + + domain: A domain name. Length - min: 1, max: 200. + + ipv4: An IPv4 address. Must be a valid IP address. + + ipv6: An IPv6 address. Must be a valid IP address. + name: type + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The string representation of the indicator + name: value + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Specify a host's ID to return only processes from that host. Get + a host's ID from GET /devices/queries/devices/v1, the Falcon console, or the + Streaming API. + name: device_id + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The first process to return, where 0 is the latest offset. Use with + the offset parameter to manage pagination of results. + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The first process to return, where 0 is the latest offset. Use with + the limit parameter to manage pagination of results. + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: search_for_indicators + label: IOCs - Search for Indicators + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The filter expression that should be used to limit the results. + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The offset to start retrieving records from. Offset and After params + are mutually exclusive. If none provided then scrolling will be used by default. + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The maximum records to return. + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The sort expression that should be used to sort the results. + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_info_about_indicators + label: Intel - Get info about indicators that match provided FQL filters + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Set the starting row number to return indicators from. Defaults to + 0. + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Set the number of indicators to return. The number must be between + 1 and 50000 + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: |- + Order fields in ascending or descending order. + + Ex: published_date|asc. + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: |- + Filter your query by specifying FQL filter parameters. Filter parameters include: + + _marker, actors, deleted, domain_types, id, indicator, ip_address_types, kill_chains, labels, labels.created_on, labels.last_valid_on, labels.name, last_updated, malicious_confidence, malware_families, published_date, reports, targets, threat_types, type, vulnerabilities. + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Perform a generic substring search across all fields. + name: q + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: If true, include both published and deleted indicators in the response. + Defaults to false. + name: include_deleted + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: download_earlier_rule_sets + label: Intel - Download earlier rule sets + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The ID of the rule set. + name: id + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Choose the format you want the rule set in. Valid formats are zip + and gzip. Defaults to zip. + name: format + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_report_ids + label: Intel - Get report IDs that match provided FQL filters + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Set the starting row number to return report IDs from. Defaults to + 0. + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Set the number of report IDs to return. The value must be between + 1 and 5000. + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: |- + Order fields in ascending or descending order. + + Ex: created_date|asc. + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: |- + Filter your query by specifying FQL filter parameters. Filter parameters include: + + actors, actors.id, actors.name, actors.slug, actors.url, created_date, description, id, last_modified_date, motivations, motivations.id, motivations.slug, motivations.value, name, name.raw, short_description, slug, sub_type, sub_type.id, sub_type.name, sub_type.slug, tags, tags.id, tags.slug, tags.value, target_countries, target_countries.id, target_countries.slug, target_countries.value, target_industries, target_industries.id, target_industries.slug, target_industries.value, type, type.id, type.name, type.slug, url. + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Perform a generic substring search across all fields. + name: q + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: search_for_rule_ids + label: Intel - Search for rule IDs that match provided filter criteria + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: |- + The rule news report type. Accepted values: + + snort-suricata-master + + snort-suricata-update + + snort-suricata-changelog + + yara-master + + yara-update + + yara-changelog + + common-event-format + + netwitness + name: type + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Set the starting row number to return reports from. Defaults to 0. + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The number of rule IDs to return. Defaults to 10. + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: |- + Order fields in ascending or descending order. + + Ex: created_date|asc. + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Search by rule title. + name: name + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Substring match on description field. + name: description + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Search for rule tags. + name: tags + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Filter results to those created on or after a certain date. + name: min_created_date + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Filter results to those created on or before a certain date. + name: max_created_date + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Perform a generic substring search across all fields. + name: q + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_info_about_reports + label: Intel - Get info about reports that match provided FQL filters + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Set the starting row number to return reports from. Defaults to 0. + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Set the number of reports to return. The value must be between 1 + and 5000. + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: 'Order fields in ascending or descending order. Ex: created_date|asc.' + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: |- + Filter your query by specifying FQL filter parameters. Filter parameters include: + + actors, actors.id, actors.name, actors.slug, actors.url, created_date, description, id, last_modified_date, motivations, motivations.id, motivations.slug, motivations.value, name, name.raw, short_description, slug, sub_type, sub_type.id, sub_type.name, sub_type.slug, tags, tags.id, tags.slug, tags.value, target_countries, target_countries.id, target_countries.slug, target_countries.value, target_industries, target_industries.id, target_industries.slug, target_industries.value, type, type.id, type.name, type.slug, url. + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Perform a generic substring search across all fields. + name: q + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: |- + The fields to return, or a predefined set of fields in the form of the collection name surrounded by two underscores like: + + \_\_\\_\_. + + Ex: slug \_\_full\_\_. + + Defaults to \_\_basic\_\_. + name: fields + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_indicators_ids + label: Intel - Get indicators IDs that match provided FQL filters + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Set the starting row number to return indicator IDs from. Defaults + to 0. + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Set the number of indicator IDs to return. The number must be between + 1 and 50000 + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: |- + Order fields in ascending or descending order. + + Ex: published_date|asc. + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: |- + Filter your query by specifying FQL filter parameters. Filter parameters include: + + _marker, actors, deleted, domain_types, id, indicator, ip_address_types, kill_chains, labels, labels.created_on, labels.last_valid_on, labels.name, last_updated, malicious_confidence, malware_families, published_date, reports, targets, threat_types, type, vulnerabilities. + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Perform a generic substring search across all fields. + name: q + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: If true, include both published and deleted indicators in the response. + Defaults to false. + name: include_deleted + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: retrieve_specific_actors_using_their_actor_ids + label: Intel - Retrieve specific actors using their actor IDs + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Generated by shuffler.io OpenAPI + name: body + example: |- + { + "ids": "${ids}" + } + value: |- + { + "ids": "${ids}" + } + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: retrieve_specific_indicators_using_their_indicator_ids + label: Intel - Retrieve specific indicators using their indicator IDs + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Generated by shuffler.io OpenAPI + name: body + example: |- + { + "ids": "${ids}" + } + value: |- + { + "ids": "${ids}" + } + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_info_about_actors + label: Intel - Get info about actors that match provided FQL filters + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Set the starting row number to return actors from. Defaults to 0. + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Set the number of actors to return. The value must be between 1 and + 5000. + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: |- + Order fields in ascending or descending order. + + Ex: created_date|asc. + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: |- + Filter your query by specifying FQL filter parameters. Filter parameters include: + + actors, actors.id, actors.name, actors.slug, actors.url, created_date, description, id, last_modified_date, motivations, motivations.id, motivations.slug, motivations.value, name, name.raw, short_description, slug, sub_type, sub_type.id, sub_type.name, sub_type.slug, tags, tags.id, tags.slug, tags.value, target_countries, target_countries.id, target_countries.slug, target_countries.value, target_industries, target_industries.id, target_industries.slug, target_industries.value, type, type.id, type.name, type.slug, url. + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Perform a generic substring search across all fields. + name: q + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: |- + The fields to return, or a predefined set of fields in the form of the collection name surrounded by two underscores like: + + \_\_\\_\_. + + Ex: slug \_\_full\_\_. + + Defaults to \_\_basic\_\_. + name: fields + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_a_report_pdf_attachment + label: Intel - Return a Report PDF attachment + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The ID of the report you want to download as a PDF. + name: id + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: download_the_latest_rule_set + label: Intel - Download the latest rule set + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: |- + The rule news report type. Accepted values: + + snort-suricata-master + + snort-suricata-update + + snort-suricata-changelog + + yara-master + + yara-update + + yara-changelog + + common-event-format + + netwitness + name: type + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Choose the format you want the rule set in. Valid formats are zip + and gzip. Defaults to zip. + name: format + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: retrieve_details_for_rule_sets_for_ids + label: Intel - Retrieve details for rule sets for the specified ids + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The ids of rules to return. + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_actor_ids + label: Intel - Get actor IDs that match provided FQL filters + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Set the starting row number to return actors IDs from. Defaults to + 0. + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Set the number of actor IDs to return. The value must be between + 1 and 5000. + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: |- + Order fields in ascending or descending order. + + Ex: created_date|asc. + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: |- + Filter your query by specifying FQL filter parameters. Filter parameters include: + + actors, actors.id, actors.name, actors.slug, actors.url, created_date, description, id, last_modified_date, motivations, motivations.id, motivations.slug, motivations.value, name, name.raw, short_description, slug, sub_type, sub_type.id, sub_type.name, sub_type.slug, tags, tags.id, tags.slug, tags.value, target_countries, target_countries.id, target_countries.slug, target_countries.value, target_industries, target_industries.id, target_industries.slug, target_industries.value, type, type.id, type.name, type.slug, url. + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Perform a generic substring search across all fields. + name: q + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: retrieve_specific_reports_using_their_report_ids + label: Intel - Retrieve specific reports using their report IDs + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The IDs of the reports you want to retrieve. + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: |- + The fields to return, or a predefined set of fields in the form of the collection name surrounded by two underscores like: + + \_\_\\_\_. + + Ex: slug \_\_full\_\_. + + Defaults to \_\_basic\_\_. + name: fields + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_rules_by_id + label: Custom IOA - Get rules by ID + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The IDs of the entities + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: delete_rules_from_a_rule_group_by_id + label: Custom IOA - Delete rules from a rule group by ID + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The parent rule group + name: rule_group_id + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The IDs of the entities + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Explains why the entity is being deleted + name: comment + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: create_a_rule_within_a_rule_group + label: Custom IOA - Create a rule within a rule group + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Generated by shuffler.io OpenAPI + name: body + example: |- + { + "comment": "${comment}", + "description": "${description}", + "disposition_id": "${disposition_id}", + "field_values": "${field_values}", + "name": "${name}", + "pattern_severity": "${pattern_severity}", + "rulegroup_id": "${rulegroup_id}", + "ruletype_id": "${ruletype_id}" + } + value: |- + { + "comment": "${comment}", + "description": "${description}", + "disposition_id": "${disposition_id}", + "field_values": "${field_values}", + "name": "${name}", + "pattern_severity": "${pattern_severity}", + "rulegroup_id": "${rulegroup_id}", + "ruletype_id": "${ruletype_id}" + } + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: update_rules_within_a_rule_group + label: Custom IOA - Update rules within a rule group + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Generated by shuffler.io OpenAPI + name: body + example: |- + { + "comment": "${comment}", + "rule_updates": "${rule_updates}", + "rulegroup_id": "${rulegroup_id}", + "rulegroup_version": "${rulegroup_version}" + } + value: |- + { + "comment": "${comment}", + "rule_updates": "${rule_updates}", + "rulegroup_id": "${rulegroup_id}", + "rulegroup_version": "${rulegroup_version}" + } + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_rule_types_by_id + label: Custom IOA - Get rule types by ID + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The IDs of the entities + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_all_platform_ids + label: Custom IOA - Get all platform IDs + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Starting index of overall result set from which to return IDs + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Number of IDs to return + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: find_all_rule_ids + label: Custom IOA - Finds all rule IDs matching the query with optional filter + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: 'Possible order by fields: {rules.ruletype_name, rules.enabled, rules.created_by, + rules.current_version.name, rules.current_version.modified_by, rules.created_on, + rules.current_version.description, rules.current_version.pattern_severity, rules.current_version.action_label, + rules.current_version.modified_on}' + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: 'FQL query specifying the filter parameters. Filter term criteria: + [enabled platform name description rules.action_label rules.name rules.description + rules.pattern_severity rules.ruletype_name rules.enabled]. Filter range criteria: + created_on, modified_on; use any common date format, such as ''2010-05-15T14:55:21.892315096Z''.' + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Match query criteria, which includes all the filter string fields + name: q + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Starting index of overall result set from which to return IDs + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Number of IDs to return + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: find_all_rule_group_ids + label: Custom IOA - Finds all rule group IDs matching the query with optional filter + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: 'Possible order by fields: {created_by, created_on, modified_by, + modified_on, enabled, name, description}' + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: 'FQL query specifying the filter parameters. Filter term criteria: + [enabled platform name description rules.action_label rules.name rules.description + rules.pattern_severity rules.ruletype_name rules.enabled]. Filter range criteria: + created_on, modified_on; use any common date format, such as ''2010-05-15T14:55:21.892315096Z''.' + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Match query criteria, which includes all the filter string fields + name: q + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Starting index of overall result set from which to return IDs + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Number of IDs to return + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_rule_groups_by_id + label: Custom IOA - Get rule groups by ID + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The IDs of the entities + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: delete_rule_groups_by_id + label: Custom IOA - Delete rule groups by ID + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The IDs of the entities + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Explains why the entity is being deleted + name: comment + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: create_a_rule_group + label: Custom IOA - Create a rule group for a platform with a name and an optional description + Returns the rule group + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Generated by shuffler.io OpenAPI + name: body + example: |- + { + "comment": "${comment}", + "description": "${description}", + "name": "${name}", + "platform": "${platform}" + } + value: |- + { + "comment": "${comment}", + "description": "${description}", + "name": "${name}", + "platform": "${platform}" + } + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: update_a_rule_group + label: Custom IOA - Update a rule group The following properties can be modified name description + enabled + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Generated by shuffler.io OpenAPI + name: body + example: |- + { + "comment": "${comment}", + "description": "${description}", + "enabled": "${enabled}", + "id": "${id}", + "name": "${name}", + "rulegroup_version": "${rulegroup_version}" + } + value: |- + { + "comment": "${comment}", + "description": "${description}", + "enabled": "${enabled}", + "id": "${id}", + "name": "${name}", + "rulegroup_version": "${rulegroup_version}" + } + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_all_rule_type_ids + label: Custom IOA - Get all rule type IDs + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Starting index of overall result set from which to return IDs + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Number of IDs to return + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_all_pattern_severity_ids + label: Custom IOA - Get all pattern severity IDs + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Starting index of overall result set from which to return IDs + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Number of IDs to return + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: validates_field_values_and_checks_for_string_matches + label: Custom IOA - Validates field values and checks for matches if a test string is provided + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Generated by shuffler.io OpenAPI + name: body + example: |- + { + "fields": "${fields}" + } + value: |- + { + "fields": "${fields}" + } + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_rules_by_id + label: Custom IOA - Get rules by ID and optionally version in the following format IDversion + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Generated by shuffler.io OpenAPI + name: body + example: |- + { + "ids": "${ids}" + } + value: |- + { + "ids": "${ids}" + } + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: find_all_rule_groups + label: Custom IOA - Find all rule groups matching the query with optional filter + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: 'Possible order by fields: {created_by, created_on, modified_by, + modified_on, enabled, name, description}' + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: 'FQL query specifying the filter parameters. Filter term criteria: + [enabled platform name description rules.action_label rules.name rules.description + rules.pattern_severity rules.ruletype_name rules.enabled]. Filter range criteria: + created_on, modified_on; use any common date format, such as ''2010-05-15T14:55:21.892315096Z''.' + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Match query criteria, which includes all the filter string fields + name: q + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Starting index of overall result set from which to return IDs + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Number of IDs to return + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_platforms_by_id + label: Custom IOA - Get platforms by ID + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The IDs of the entities + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_pattern_severities_by_id + label: Custom IOA - Get pattern severities by ID + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The IDs of the entities + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_a_zipped_sample + label: Malquery - Fetch a zip archive with password infected containing the samples Call this + once the entitiessamplesmultidownload request has finished processing + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Multidownload job id + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: schedule_samples_for_download + label: Malquery - Schedule samples for download Use the result id with the request endpoint + to check if the download is ready after which you can call the entitiessamplesfetch + to get the zip + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Generated by shuffler.io OpenAPI + name: body + example: |- + { + "samples": "${samples}" + } + value: |- + { + "samples": "${samples}" + } + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: search_falcon_malquery + label: Malquery - Search Falcon MalQuery for a combination of hex patterns and strings in order + to identify samples based upon file content at byte level granularity You can + filter results on criteria such as file type file size and first seen date Returns + a request id which can be used with the request endpoint + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Generated by shuffler.io OpenAPI + name: body + example: |- + { + "options": "${options}", + "patterns": "${patterns}" + } + value: |- + { + "options": "${options}", + "patterns": "${patterns}" + } + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_information_about_search_and_download_quotas + label: Malquery - Get information about search and download quotas in your environment + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: retrieve_indexed_files_metadata_by_their_hash + label: Malquery - Retrieve indexed files metadata by their hash + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The file SHA256. + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: schedule_a_yara_based_search_for_execution + label: Malquery - Schedule a YARAbased search for execution Returns a request id which can + be used with the request endpoint + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Generated by shuffler.io OpenAPI + name: body + example: |- + { + "options": "${options}", + "yara_rule": "${yara_rule}" + } + value: |- + { + "options": "${options}", + "yara_rule": "${yara_rule}" + } + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: check_the_status_and_results_of_an_asynchronous_request + label: Malquery - Check the status and results of an asynchronous request such as hunt or exactsearch + Supports a single request id at this time + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Identifier of a MalQuery request + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: download_a_file_indexed_by_malquery + label: Malquery - Download a file indexed by MalQuery Specify the file using its SHA256 Only + one file is supported at this time + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The file SHA256. + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: revoke_oauth2_access_token + label: OAuth2 - Revoke a previously issued OAuth2 access token before the end of its standard + 30minute lifespan + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Accept-Encoding: application/json + Content-Type: application/x-www-form-urlencoded + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: search_for_device_control_policy_ids + label: Device Control Policies - Search for Device Control Policies in your environment by providing an FQL + filter and paging details Returns a set of Device Control Policy IDs which match + the filter criteria + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The filter expression that should be used to limit the results + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The offset to start retrieving records from + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The maximum records to return. [1-5000] + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The property to sort by + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: search_for_device_control_policy_members + label: Device Control Policies - Search for members of a Device Control Policy in your environment by providing + an FQL filter and paging details Returns a set of host details which match the + filter criteria + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The ID of the Device Control Policy to search for members of + name: id + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The filter expression that should be used to limit the results + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The offset to start retrieving records from + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The maximum records to return. [1-5000] + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The property to sort by + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: search_for_device_control_policies + label: Device Control Policies - Search for Device Control Policies in your environment by providing an FQL + filter and paging details Returns a set of Device Control Policies which match + the filter criteria + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The filter expression that should be used to limit the results + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The offset to start retrieving records from + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The maximum records to return. [1-5000] + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The property to sort by + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: search_for_device_control_policy_member_ids + label: Device Control Policies - Search for members of a Device Control Policy in your environment by providing + an FQL filter and paging details Returns a set of Agent IDs which match the filter + criteria + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The ID of the Device Control Policy to search for members of + name: id + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The filter expression that should be used to limit the results + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The offset to start retrieving records from + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The maximum records to return. [1-5000] + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The property to sort by + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: set_precedence_of_device_control_policies + label: Device Control Policies - Sets the precedence of Device Control Policies based on the order of IDs + specified in the request The first ID specified will have the highest precedence + and the last ID specified will have the lowest You must specify all nonDefault + Policies for a platform when updating precedence + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: perform_action_on_the_device_control_policies + label: Device Control Policies - Perform the specified action on the Device Control Policies specified in + the request + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The action to perform + name: action_name + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: retrieve_a_set_of_device_control_policies + label: Device Control Policies - Retrieve a set of Device Control Policies by specifying their IDs + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The IDs of the Device Control Policies to return + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: delete_a_set_of_device_control_policies + label: Device Control Policies - Delete a set of Device Control Policies by specifying their IDs + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The IDs of the Device Control Policies to delete + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: create_device_control_policies + label: Device Control Policies - Create Device Control Policies by specifying details about the policy to + create + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: update_device_control_policies + label: Device Control Policies - Update Device Control Policies by specifying the ID of the policy and details + to update + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: search_for_firewall_policies + label: Firewall Policies - Search for Firewall Policies in your environment by providing an FQL filter + and paging details Returns a set of Firewall Policy IDs which match the filter + criteria + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The filter expression that should be used to limit the results + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The offset to start retrieving records from + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The maximum records to return. [1-5000] + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The property to sort by + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: set_precedence_of_firewall_policies + label: Firewall Policies - Sets the precedence of Firewall Policies based on the order of IDs specified + in the request The first ID specified will have the highest precedence and the + last ID specified will have the lowest You must specify all nonDefault Policies + for a platform when updating precedence + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: perform_action_on_the_firewall_policies + label: Firewall Policies - Perform the specified action on the Firewall Policies specified in the request + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The action to perform + name: action_name + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: search_for_firewall_policy_member_ids + label: Firewall Policies - Search for members of a Firewall Policy in your environment by providing + an FQL filter and paging details Returns a set of Agent IDs which match the filter + criteria + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The ID of the Firewall Policy to search for members of + name: id + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The filter expression that should be used to limit the results + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The offset to start retrieving records from + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The maximum records to return. [1-5000] + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The property to sort by + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: search_for_firewall_policies + label: Firewall Policies - Search for Firewall Policies in your environment by providing an FQL filter + and paging details Returns a set of Firewall Policies which match the filter criteria + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The filter expression that should be used to limit the results + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The offset to start retrieving records from + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The maximum records to return. [1-5000] + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The property to sort by + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: retrieve_a_set_of_firewall_policies + label: Firewall Policies - Retrieve a set of Firewall Policies by specifying their IDs + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The IDs of the Firewall Policies to return + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: delete_a_set_of_firewall_policies + label: Firewall Policies - Delete a set of Firewall Policies by specifying their IDs + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The IDs of the Firewall Policies to delete + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: create_firewall_policies + label: Firewall Policies - Create Firewall Policies by specifying details about the policy to create + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The policy ID to be cloned from + name: clone_id + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: update_firewall_policies + label: Firewall Policies - Update Firewall Policies by specifying the ID of the policy and details to + update + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: search_for_firewall_policy_members + label: Firewall Policies - Search for members of a Firewall Policy in your environment by providing + an FQL filter and paging details Returns a set of host details which match the + filter criteria + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The ID of the Firewall Policy to search for members of + name: id + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The filter expression that should be used to limit the results + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The offset to start retrieving records from + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The maximum records to return. [1-5000] + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The property to sort by + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: search_for_prevention_policy_members + label: Prevention Policies - Search for members of a Prevention Policy + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The ID of the Prevention Policy to search for members of + name: id + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The filter expression that should be used to limit the results + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The offset to start retrieving records from + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The maximum records to return. [1-5000] + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The property to sort by + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: search_for_prevention_policy_ids + label: Prevention Policies - Search for Prevention Policies in your environment by providing an FQL filter + and paging details Returns a set of Prevention Policy IDs which match the filter + criteria + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The filter expression that should be used to limit the results + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The offset to start retrieving records from + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The maximum records to return. [1-5000] + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The property to sort by + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: search_for_prevention_policies + label: Prevention Policies - Search for Prevention Policies in your environment by providing an FQL filter + and paging details Returns a set of Prevention Policies which match the filter + criteria + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The filter expression that should be used to limit the results + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The offset to start retrieving records from + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The maximum records to return. [1-5000] + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The property to sort by + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: set_precedence_of_prevention_policies + label: Prevention Policies - Sets the precedence of Prevention Policies based on the order of IDs specified + in the request The first ID specified will have the highest precedence and the + last ID specified will have the lowest You must specify all nonDefault Policies + for a platform when updating precedence + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: retrieve_a_set_of_prevention_policies + label: Prevention Policies - Retrieve a set of Prevention Policies by specifying their IDs + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The IDs of the Prevention Policies to return + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: delete_a_set_of_prevention_policies + label: Prevention Policies - Delete a set of Prevention Policies by specifying their IDs + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The IDs of the Prevention Policies to delete + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: create_prevention_policies + label: Prevention Policies - Create Prevention Policies by specifying details about the policy to create + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: update_prevention_policies + label: Prevention Policies - Update Prevention Policies by specifying the ID of the policy and details + to update + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: search_for_prevention_policy_member_ids + label: Prevention Policies - Search for members of a Prevention Policy in your environment by providing + an FQL filter and paging details Returns a set of Agent IDs which match the filter + criteria + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The ID of the Prevention Policy to search for members of + name: id + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The filter expression that should be used to limit the results + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The offset to start retrieving records from + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The maximum records to return. [1-5000] + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The property to sort by + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: perform_action_on_the_prevention_policies + label: Prevention Policies - Perform the specified action on the Prevention Policies specified in the + request + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The action to perform + name: action_name + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: set_precedence_of_response_policies + label: Response Policies - Sets the precedence of Response Policies based on the order of IDs specified + in the request The first ID specified will have the highest precedence and the + last ID specified will have the lowest You must specify all nonDefault Policies + for a platform when updating precedence + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: search_for_response_policy_members + label: Response Policies - Search for members of a Response policy in your environment by providing + an FQL filter and paging details Returns a set of host details which match the + filter criteria + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The ID of the Response policy to search for members of + name: id + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The filter expression that should be used to limit the results + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The offset to start retrieving records from + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The maximum records to return. [1-5000] + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The property to sort by + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: search_for_response_policy_member_ids + label: Response Policies - Search for members of a Response policy in your environment by providing + an FQL filter and paging details Returns a set of Agent IDs which match the filter + criteria + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The ID of the Response policy to search for members of + name: id + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The filter expression that should be used to limit the results + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The offset to start retrieving records from + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The maximum records to return. [1-5000] + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The property to sort by + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: perform_action_on_the_response_policies + label: Response Policies - Perform the specified action on the Response Policies specified in the request + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The action to perform + name: action_name + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: retrieve_a_set_of_response_policies + label: Response Policies - Retrieve a set of Response Policies by specifying their IDs + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The IDs of the RTR Policies to return + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: delete_a_set_of_response_policies + label: Response Policies - Delete a set of Response Policies by specifying their IDs + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The IDs of the Response Policies to delete + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: create_response_policies + label: Response Policies - Create Response Policies by specifying details about the policy to create + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: update_response_policies + label: Response Policies - Update Response Policies by specifying the ID of the policy and details to + update + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: search_for_response_policy_ids + label: Response Policies - Search for Response Policies in your environment by providing an FQL filter + with sort andor paging details This returns a set of Response Policy IDs that + match the given criteria + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The filter expression that should be used to determine the results. + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The offset of the first record to retrieve from + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The maximum number of records to return [1-5000] + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The property to sort results by + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: search_for_response_policies + label: Response Policies - Search for Response Policies in your environment by providing an FQL filter + and paging details Returns a set of Response Policies which match the filter criteria + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The filter expression that should be used to limit the results + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The offset to start retrieving records from + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The maximum records to return. [1-5000] + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The property to sort by + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: search_for_sensor_update_policies + label: Sensor Update Policies - Search for Sensor Update Policies in your environment by providing an FQL + filter and paging details Returns a set of Sensor Update Policies which match + the filter criteria + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The filter expression that should be used to limit the results + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The offset to start retrieving records from + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The maximum records to return. [1-5000] + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The property to sort by + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: search_for_sensor_update_policy_member_ids + label: Sensor Update Policies - Search for members of a Sensor Update Policy in your environment by providing + an FQL filter and paging details Returns a set of Agent IDs which match the filter + criteria + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The ID of the Sensor Update Policy to search for members of + name: id + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The filter expression that should be used to limit the results + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The offset to start retrieving records from + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The maximum records to return. [1-5000] + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The property to sort by + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: perform_action_on_the_sensor_update_policies + label: Sensor Update Policies - Perform the specified action on the Sensor Update Policies specified in the + request + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The action to perform + name: action_name + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: search_for_sensor_update_policy_members + label: Sensor Update Policies - Search for members of a Sensor Update Policy in your environment by providing + an FQL filter and paging details Returns a set of host details which match the + filter criteria + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The ID of the Sensor Update Policy to search for members of + name: id + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The filter expression that should be used to limit the results + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The offset to start retrieving records from + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The maximum records to return. [1-5000] + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The property to sort by + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: retrieve_available_builds_for_use_with_sensor_update_policies + label: Sensor Update Policies - Retrieve available builds for use with Sensor Update Policies + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The platform to return builds for + name: platform + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: search_for_sensor_update_policy_ids + label: Sensor Update Policies - Search for Sensor Update Policies in your environment by providing an FQL + filter and paging details Returns a set of Sensor Update Policy IDs which match + the filter criteria + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The filter expression that should be used to limit the results + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The offset to start retrieving records from + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The maximum records to return. [1-5000] + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The property to sort by + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: search_for_sensor_update_policies_with_additional_support_for_uninstall_protection + label: Sensor Update Policies - Search for Sensor Update Policies with additional support for uninstall protection + in your environment by providing an FQL filter and paging details Returns a set + of Sensor Update Policies which match the filter criteria + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The filter expression that should be used to limit the results + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The offset to start retrieving records from + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The maximum records to return. [1-5000] + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The property to sort by + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: retrieve_a_set_of_sensor_update_policies_with_additional_support_for_uninstall_protection + label: Sensor Update Policies - Retrieve a set of Sensor Update Policies with additional support for uninstall + protection by specifying their IDs + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The IDs of the Sensor Update Policies to return + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: create_sensor_update_policies + label: Sensor Update Policies - Create Sensor Update Policies by specifying details about the policy to create + with additional support for uninstall protection + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: update_sensor_update_policies + label: Sensor Update Policies - Update Sensor Update Policies by specifying the ID of the policy and details + to update with additional support for uninstall protection + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: retrieve_an_uninstall_token_for_a_specific_device + label: Sensor Update Policies - Reveals an uninstall token for a specific device To retrieve the bulk maintenance + token pass the value MAINTENANCE as the value for device_id + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: set_precedence_of_sensor_update_policies + label: Sensor Update Policies - Sets the precedence of Sensor Update Policies based on the order of IDs specified + in the request The first ID specified will have the highest precedence and the + last ID specified will have the lowest You must specify all nonDefault Policies + for a platform when updating precedence + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: retrieve_a_set_of_sensor_update_policies + label: Sensor Update Policies - Retrieve a set of Sensor Update Policies by specifying their IDs + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The IDs of the Sensor Update Policies to return + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: delete_a_set_of_sensor_update_policies + label: Sensor Update Policies - Delete a set of Sensor Update Policies by specifying their IDs + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The IDs of the Sensor Update Policies to delete + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: create_sensor_update_policies + label: Sensor Update Policies - Create Sensor Update Policies by specifying details about the policy to create + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: update_sensor_update_policies + label: Sensor Update Policies - Update Sensor Update Policies by specifying the ID of the policy and details + to update + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_a_set_of_ioa_exclusions + label: IOA Exclusions - Get a set of IOA Exclusions by specifying their IDs + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The ids of the exclusions to retrieve + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: delete_the_ioa_exclusions_by_id + label: IOA Exclusions - Delete the IOA exclusions by id + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The ids of the exclusions to delete + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Explains why this exclusions was deleted + name: comment + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: create_the_ioa_exclusions + label: IOA Exclusions - Create the IOA exclusions + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: update_the_ioa_exclusions + label: IOA Exclusions - Update the IOA exclusions + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: search_for_ioa_exclusions + label: IOA Exclusions - Search for IOA exclusions + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The filter expression that should be used to limit the results. + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The offset to start retrieving records from + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The maximum records to return. [1-500] + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The sort expression that should be used to sort the results. + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: search_for_ml_exclusions + label: ML Exclusions - Search for ML exclusions + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The filter expression that should be used to limit the results. + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The offset to start retrieving records from + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The maximum records to return. [1-500] + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The sort expression that should be used to sort the results. + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_a_set_of_ml_exclusions + label: ML Exclusions - Get a set of ML Exclusions by specifying their IDs + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The ids of the exclusions to retrieve + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: delete_the_ml_exclusions_by_id + label: ML Exclusions - Delete the ML exclusions by id + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The ids of the exclusions to delete + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Explains why this exclusions was deleted + name: comment + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: create_the_ml_exclusions + label: ML Exclusions - Create the ML exclusions + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: update_the_ml_exclusions + label: ML Exclusions - Update the ML exclusions + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_a_set_of_sensor_visibility_exclusions + label: Sensor Visibility Exclusions - Get a set of Sensor Visibility Exclusions by specifying their IDs + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The ids of the exclusions to retrieve + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: delete_the_sensor_visibility_exclusions_by_id + label: Sensor Visibility Exclusions - Delete the sensor visibility exclusions by id + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The ids of the exclusions to delete + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Explains why this exclusions was deleted + name: comment + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: create_the_sensor_visibility_exclusions + label: Sensor Visibility Exclusions - Create the sensor visibility exclusions + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: update_the_sensor_visibility_exclusions + label: Sensor Visibility Exclusions - Update the sensor visibility exclusions + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: search_for_sensor_visibility_exclusions + label: Sensor Visibility Exclusions - Search for sensor visibility exclusions + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The filter expression that should be used to limit the results. + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The offset to start retrieving records from + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The maximum records to return. [1-500] + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The sort expression that should be used to sort the results. + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_status_of_an_executed_active_responder_command_on_a_single_host + label: Real Time Response - Get status of an executed active_responder command on a single host + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Cloud Request ID of the executed command to query + name: cloud_request_id + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Sequence ID that we want to retrieve. Command responses are chunked + across sequences + name: sequence_id + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: execute_an_active_responder_command_on_a_single_host + label: Real Time Response - Execute an active responder command on a single host + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: batch_refresh_a_rtr_session_on_multiple_hosts_rtr_sessions_will_expire_after_10_minutes_unless_refreshed + label: Real Time Response - Batch refresh a RTR session on multiple hosts RTR sessions will expire after + 10 minutes unless refreshed + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Timeout for how long to wait for the request in seconds, default + timeout is 30 seconds. Maximum is 10 minutes. + name: timeout + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: 'Timeout duration for for how long to wait for the request in duration + syntax. Example, `10s`. Valid units: `ns, us, ms, s, m, h`. Maximum is 10 minutes.' + name: timeout_duration + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_queued_session_metadata_by_session_id + label: Real Time Response - Get queued session metadata by session ID + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: refresh_a_session_timeout_on_a_single_host + label: Real Time Response - Refresh a session timeout on a single host + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: batch_initialize_a_rtr_session_on_multiple_hosts__before_any_rtr_commands_can_be_used_an_active_session_is_needed_on_the_host + label: Real Time Response - Batch initialize a RTR session on multiple hosts Before any RTR commands + can be used an active session is needed on the host + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Timeout for how long to wait for the request in seconds, default + timeout is 30 seconds. Maximum is 10 minutes. + name: timeout + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: 'Timeout duration for for how long to wait for the request in duration + syntax. Example, `10s`. Valid units: `ns, us, ms, s, m, h`. Maximum is 10 minutes.' + name: timeout_duration + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_rtr_extracted_file_contents_for_specified_session_and_sha256 + label: Real Time Response - Get RTR extracted file contents for specified session and sha256 + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: RTR Session id + name: session_id + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Extracted SHA256 (e.g. 'efa256a96af3b556cd3fc9d8b1cf587d72807d7805ced441e8149fc279db422b') + name: sha256 + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Filename to use for the archive name and the file within the archive. + name: filename + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_aggregates_on_session_data + label: Real Time Response - Get aggregates on session data + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: delete_a_session + label: Real Time Response - Delete a session + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: RTR Session id + name: session_id + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: initialize_a_new_session_with_the_rtr_cloud + label: Real Time Response - Initialize a new session with the RTR cloud + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: delete_a_queued_session_command + label: Real Time Response - Delete a queued session command + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: RTR Session id + name: session_id + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Cloud Request ID of the executed command to query + name: cloud_request_id + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_a_list_of_session_ids + label: Real Time Response - Get a list of session_ids + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Starting index of overall result set from which to return ids. + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Number of ids to return. + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: 'Sort by spec. Ex: ''date_created|asc''.' + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Optional filter criteria in the form of an FQL query. For more information + about FQL queries, see our [FQL documentation in Falcon](https://falcon.crowdstrike.com/support/documentation/45/falcon-query-language-feature-guide). + "user_id" can accept a special value '@me' which will restrict results to records + with current user's ID. + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: retrieve_the_status_of_batch_get_command__will_return_successful_files_when_they_are_finished_processing + label: Real Time Response - retrieve the status of the specified batch get command Will return successful + files when they are finished processing + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Batch Get Command Request ID received from `/real-time-response/combined/get-command/v1` + name: batch_get_cmd_req_id + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Timeout for how long to wait for the request in seconds, default + timeout is 30 seconds. Maximum is 10 minutes. + name: timeout + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: 'Timeout duration for for how long to wait for the request in duration + syntax. Example, `10s`. Valid units: `ns, us, ms, s, m, h`. Maximum is 10 minutes.' + name: timeout_duration + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: batch_executes_get_command_across_hosts_to_retrieve_files_after_this_call_is_made_get_realtimeresponsecombinedbatchgetcommandv1_is_used_to_query_for_the_results + label: Real Time Response - Batch executes get command across hosts to retrieve files After this call + is made GET realtimeresponsecombinedbatchgetcommandv1 is used to query for the + results + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Timeout for how long to wait for the request in seconds, default + timeout is 30 seconds. Maximum is 10 minutes. + name: timeout + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: 'Timeout duration for for how long to wait for the request in duration + syntax. Example, `10s`. Valid units: `ns, us, ms, s, m, h`. Maximum is 10 minutes.' + name: timeout_duration + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: batch_executes_a_rtr_readonly_command + label: Real Time Response - Batch executes a RTR readonly command across the hosts mapped to the given + batch ID + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Timeout for how long to wait for the request in seconds, default + timeout is 30 seconds. Maximum is 10 minutes. + name: timeout + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: 'Timeout duration for for how long to wait for the request in duration + syntax. Example, `10s`. Valid units: `ns, us, ms, s, m, h`. Maximum is 10 minutes.' + name: timeout_duration + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_session_metadata_by_session_id + label: Real Time Response - Get session metadata by session id + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_a_list_of_files_for_rtr_session + label: Real Time Response - Get a list of files for the specified RTR session + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: RTR Session id + name: session_id + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: delete_a_rtr_session_file + label: Real Time Response - Delete a RTR session file + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: RTR Session file id + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: RTR Session id + name: session_id + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_status_of_an_executed_command_on_a_single_host + label: Real Time Response - Get status of an executed command on a single host + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Cloud Request ID of the executed command to query + name: cloud_request_id + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Sequence ID that we want to retrieve. Command responses are chunked + across sequences + name: sequence_id + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: execute_a_command_on_a_single_host + label: Real Time Response - Execute a command on a single host + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: batch_executes_a_rtr_active_responder_command + label: Real Time Response - Batch executes a RTR active_responder command across the hosts mapped to the + given batch ID + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Timeout for how long to wait for the request in seconds, default + timeout is 30 seconds. Maximum is 10 minutes. + name: timeout + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: 'Timeout duration for for how long to wait for the request in duration + syntax. Example, `10s`. Valid units: `ns, us, ms, s, m, h`. Maximum is 10 minutes.' + name: timeout_duration + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_putfiles_based_on_the_ids_given + label: Real Time Response Admin - Get putfiles based on the IDs given These are used for the RTR put command + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: File IDs + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: delete_a_putfile_based_on_the_ids_given + label: Real Time Response Admin - Delete a putfile based on the ID given Can only delete one file at a time + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: File id + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: upload_a_new_putfile_to_use_for_the_rtr_put_command + label: Real Time Response Admin - Upload a new putfile to use for the RTR put command + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_status_of_an_executed_rtr_administrator_command_on_a_single_host + label: Real Time Response Admin - Get status of an executed RTR administrator command on a single host + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Cloud Request ID of the executed command to query + name: cloud_request_id + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Sequence ID that we want to retrieve. Command responses are chunked + across sequences + name: sequence_id + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: execute_a_rtr_administrator_command_on_a_single_host + label: Real Time Response Admin - Execute a RTR administrator command on a single host + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_a_list_of_putfile_ids + label: Real Time Response Admin - Get a list of putfile IDs that are available to the user for the put command + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Optional filter criteria in the form of an FQL query. For more information + about FQL queries, see our [FQL documentation in Falcon](https://falcon.crowdstrike.com/support/documentation/45/falcon-query-language-feature-guide). + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Starting index of overall result set from which to return ids. + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Number of ids to return. + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: 'Sort by spec. Ex: ''created_at|asc''.' + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_a_list_of_custom_script_ids + label: Real Time Response Admin - Get a list of custom_script IDs that are available to the user for the runscript + command + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Optional filter criteria in the form of an FQL query. For more information + about FQL queries, see our [FQL documentation in Falcon](https://falcon.crowdstrike.com/support/documentation/45/falcon-query-language-feature-guide). + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Starting index of overall result set from which to return ids. + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Number of ids to return. + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: 'Sort by spec. Ex: ''created_at|asc''.' + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_custom_scripts_based_on_the_ids_given + label: Real Time Response Admin - Get custom_scripts based on the IDs given These are used for the RTR runscript + command + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: File IDs + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: delete_a_custom_script_based_on_the_id_given + label: Real Time Response Admin - Delete a custom_script based on the ID given Can only delete one script at + a time + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: File id + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: upload_a_new_custom_script_to_use + label: Real Time Response Admin - Upload a new custom_script to use for the RTR runscript command + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: upload_a_new_scripts_to_replace_an_existing_one + label: Real Time Response Admin - Upload a new scripts to replace an existing one + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: batch_executes_a_rtr_administrator_command + label: Real Time Response Admin - Batch executes a RTR administrator command across the hosts mapped to the + given batch ID + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Timeout for how long to wait for the request in seconds, default + timeout is 30 seconds. Maximum is 10 minutes. + name: timeout + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: 'Timeout duration for for how long to wait for the request in duration + syntax. Example, `10s`. Valid units: `ns, us, ms, s, m, h`. Maximum is 10 minutes.' + name: timeout_duration + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: delete_notifications_based_on_ids_notifications + label: Recon - Delete notifications based on IDs Notifications cannot be recovered after + they are deleted + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Notifications IDs. + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: update_notification_status_or_assignee + label: Recon - Update notification status or assignee Accepts bulk requests + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: query_notifications + label: Recon - Query notifications based on provided criteria Use the IDs from this response + to get the notification entities on GET entitiesnotificationsv1 or GET entitiesnotificationsdetailedv1 + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Starting index of overall result set from which to return ids. + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Number of ids to return. + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: 'Possible order by fields: created_date, updated_date. Ex: ''updated_date|desc''.' + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: 'FQL query to filter notifications by. Possible filter properties + are: [id cid user_uuid status rule_id rule_name rule_topic rule_priority item_type + created_date updated_date]' + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Free text search across all indexed fields. + name: q + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_detailed_notifications_based_on_their_ids + label: Recon - Get detailed notifications based on their IDs These include the raw intelligence + content that generated the matchThis endpoint will return translated notification + content The only target language available is English A single notification can + be translated per request + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Notification IDs. + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: preview_rules_notification_count_and_distribution + label: Recon - Preview rules notification count and distribution This will return aggregations + on channel count site + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_notification_aggregates + label: Recon - Get notification aggregates + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_actions_based_on_their_ids + label: Recon - Get actions based on their IDs IDs can be retrieved using the GET queriesactionsv1 + endpoint + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Action IDs. + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: delete_an_action_from_a_monitoring_rule_based_on_the_action_id + label: Recon - Delete an action from a monitoring rule based on the action ID + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: ID of the action. + name: id + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: create_actions_for_a_monitoring_rule + label: Recon - Create actions for a monitoring rule Accepts a list of actions that will + be attached to the monitoring rule + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Generated by shuffler.io OpenAPI + name: body + example: |- + { + "actions": "${actions}", + "rule_id": "${rule_id}" + } + value: |- + { + "actions": "${actions}", + "rule_id": "${rule_id}" + } + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: update_an_action_for_a_monitoring_rule + label: Recon - Update an action for a monitoring rule + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Generated by shuffler.io OpenAPI + name: body + example: |- + { + "frequency": "${frequency}", + "id": "${id}", + "recipients": "${recipients}", + "status": "${status}" + } + value: |- + { + "frequency": "${frequency}", + "id": "${id}", + "recipients": "${recipients}", + "status": "${status}" + } + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: query_actions + label: Recon - Query actions based on provided criteria Use the IDs from this response to + get the action entities on GET entitiesactionsv1 + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Starting index of overall result set from which to return IDs. + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Number of IDs to return. + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: 'Possible order by fields: created_timestamp, updated_timestamp. + Ex: ''updated_timestamp|desc''.' + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: 'FQL query to filter actions by. Possible filter properties are: + [id cid user_uuid rule_id type frequency recipients status created_timestamp + updated_timestamp]' + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Free text search across all indexed fields + name: q + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: query_monitoring_rules + label: Recon - Query monitoring rules based on provided criteria Use the IDs from this response + to fetch the rules on entitiesrulesv1 + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Starting index of overall result set from which to return ids. + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Number of ids to return. + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: 'Possible order by fields: created_timestamp, last_updated_timestamp. + Ex: ''last_updated_timestamp|desc''.' + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: 'FQL query to filter rules by. Possible filter properties are: [id + cid user_uuid topic priority permissions filter status created_timestamp last_updated_timestamp]' + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Free text search across all indexed fields. + name: q + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_notifications_based_on_their_ids + label: Recon - Get notifications based on their IDs IDs can be retrieved using the GET queriesnotificationsv1 + endpoint This endpoint will return translated notification content The only target + language available is English + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Notification IDs. + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_detailed_notifications_based_on_their_ids_with_raw_intelligence_content_that_generated_the_match + label: Recon - Get detailed notifications based on their IDs These include the raw intelligence + content that generated the match + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Notification IDs. + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_monitoring_rules_rules_by_provided_ids + label: Recon - Get monitoring rules rules by provided IDs + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: IDs of rules. + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: delete_monitoring_rules + label: Recon - Delete monitoring rules + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: IDs of rules. + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: create_monitoring_rules + label: Recon - Create monitoring rules + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: update_monitoring_rules + label: Recon - Update monitoring rules + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_notifications_based_on_their_ids + label: Recon - Get notifications based on their IDs IDs can be retrieved using the GET queriesnotificationsv1 + endpoint + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Notification IDs. + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: upload_a_file_for_further_cloud_analysis + label: Sample Uploads - Upload a file for further cloud analysis After uploading call the specific + analysis API endpoint + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Name of the file. + name: file_name + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: A descriptive comment to identify the file for other users. + name: comment + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: "Defines visibility of this file in Falcon MalQuery, either via the + API or the Falcon console.\n\n- `true`: File is only shown to users within your + customer account\n- `false`: File can be seen by other CrowdStrike customers + \n\nDefault: `true`." + name: is_confidential + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: retrieve_the_file_associated_with_the_given_id_sha256 + label: Sample Uploads - retrieve the file associated with the given ID SHA256 + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The file SHA256. + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Flag whether the sample should be zipped and password protected with + pass='infected' + name: password_protected + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: find_ids_for_submitted_scans + label: Quick Scan - Find IDs for submitted scans by providing an FQL filter and paging details + Returns a set of volume IDs that match your criteria + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Optional filter and sort criteria in the form of an FQL query. For + more information about FQL queries, see [our FQL documentation in Falcon](https://falcon.crowdstrike.com/support/documentation/45/falcon-query-language-feature-guide). + name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The offset to start retrieving submissions from. + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: 'Maximum number of volume IDs to return. Max: 5000.' + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: 'Sort order: `asc` or `desc`.' + name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_scans_aggregations + label: Quick Scan - Get scans aggregations + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Generated by shuffler.io OpenAPI + name: body + example: |- + { + "date_ranges": "${date_ranges}", + "field": "${field}", + "filter": "${filter}", + "interval": "${interval}", + "min_doc_count": "${min_doc_count}", + "missing": "${missing}", + "name": "${name}", + "q": "${q}", + "ranges": "${ranges}", + "size": "${size}", + "sort": "${sort}", + "sub_aggregates": "${sub_aggregates}", + "time_zone": "${time_zone}", + "type": "${type}" + } + value: |- + { + "date_ranges": "${date_ranges}", + "field": "${field}", + "filter": "${filter}", + "interval": "${interval}", + "min_doc_count": "${min_doc_count}", + "missing": "${missing}", + "name": "${name}", + "q": "${q}", + "ranges": "${ranges}", + "size": "${size}", + "sort": "${sort}", + "sub_aggregates": "${sub_aggregates}", + "time_zone": "${time_zone}", + "type": "${type}" + } + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: check_the_status_of_a_volume_scan + label: Quick Scan - Check the status of a volume scan Time required for analysis increases with + the number of samples in a volume but usually it should take less than 1 minute + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: ID of a submitted scan + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: submit_a_volume_of_files_for_ml_scanning + label: Quick Scan - Submit a volume of files for ml scanning Time required for analysis increases + with the number of samples in a volume but usually it should take less than 1 + minute + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_sensor_installer_ids_by_provided_query + label: Sensor Download - Get sensor installer IDs by provided query + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The first item to return, where 0 is the latest item. Use with the + limit parameter to manage pagination of results. + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: 'The number of items to return in this response (default: 100, max: + 500). Use with the offset parameter to manage pagination of results.' + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: |- + Sort items using their properties. Common sort options include: + +

  • version|asc
  • release_date|desc
+ name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: |- + Filter items using a query in Falcon Query Language (FQL). An asterisk wildcard * includes all results. + + Common filter options include: +
  • platform:"windows"
  • version:>"5.2"
+ name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_sensor_installer_details_by_provided_query + label: Sensor Download - Get sensor installer details by provided query + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: The first item to return, where 0 is the latest item. Use with the + limit parameter to manage pagination of results. + name: offset + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: 'The number of items to return in this response (default: 100, max: + 500). Use with the offset parameter to manage pagination of results.' + name: limit + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: |- + Sort items using their properties. Common sort options include: + +
  • version|asc
  • release_date|desc
+ name: sort + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: |- + Filter items using a query in Falcon Query Language (FQL). An asterisk wildcard * includes all results. + + Common filter options include: +
  • platform:"windows"
  • version:>"5.2"
+ name: filter + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_sensor_installer_details_by_provided_sha256_ids + label: Sensor Download - Get sensor installer details by provided SHA256 IDs + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: The IDs of the installers + name: ids + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: download_sensor_installer_by_sha256_id + label: Sensor Download - Download sensor installer by SHA256 ID + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: SHA256 of the installer to download + name: id + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_ccid_to_use_with_sensor_installers + label: Sensor Download - Get CCID to use with sensor installers + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: refresh_an_active_event_stream + label: Event Streams - Refresh an active event stream Use the URL shown in a GET sensorsentitiesdatafeedv2 + response + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: Action name. Allowed value is refresh_active_stream_session. + name: action_name + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: 'Label that identifies your connection. Max: 32 alphanumeric characters + (a-z, A-Z, 0-9).' + name: appId + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Generated by shuffler.io OpenAPI + name: partition + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +- description: "" + name: get_all_event_streams + label: Event Streams - Discover all event streams in your environment + nodetype: action + environment: Shuffle + sharing: false + privateid: "" + publicid: "" + appid: "" + tags: [] + tested: false + parameters: + - description: 'Label that identifies your connection. Max: 32 alphanumeric characters + (a-z, A-Z, 0-9).' + name: appId + example: "" + multiline: false + options: [] + required: true + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit headers + name: headers + example: "" + value: |- + Authorization: Bearer $auth.access_token + Accept-Encoding: application/json + Content-Type: application/json + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: Add or edit queries + name: queries + example: view=basic&redirect=test + multiline: true + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + - description: 'Format for streaming events. Valid values: json, flatjson' + name: format + example: "" + multiline: false + options: [] + required: false + configuration: false + tags: [] + schema: + type: string + skip_multicheck: false + unique_toggled: false + executionvariable: + description: "" + id: "" + name: "" + value: "" + returns: + example: "" + schema: + type: string + authenticationid: "" + example: "" + auth_not_required: false + source_workflow: "" +authentication: + required: true + parameters: + - description: "" + id: "" + name: client_id + example: '******' + value: "" + multiline: false + required: true + in: "" + schema: + type: string + scheme: "" + - description: "" + id: "" + name: client_secret + example: '******' + value: "" + multiline: false + required: true + in: "" + schema: + type: string + scheme: "" + - description: The URL of the app + id: "" + name: url + example: https://api.crowdstrike.com + value: https://api.crowdstrike.com + multiline: false + required: true + in: "" + schema: + type: string + scheme: "" +tags: [] +categories: [] +created: 0 +edited: 0 +lastruntime: 0 +versions: [] +loopversions: [] +owner: b5ee0878-2de4-4182-92af-bf67ec6526f5 +public: false +referenceorg: "" +referenceurl: "" +large_image: data:image/jpeg;base64,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 diff --git a/unsupported/crowdstrike-falcon/1.0.0/requirements.txt b/unsupported/crowdstrike-falcon/1.0.0/requirements.txt new file mode 100644 index 00000000..f76ae497 --- /dev/null +++ b/unsupported/crowdstrike-falcon/1.0.0/requirements.txt @@ -0,0 +1 @@ +# No extra requirements needed diff --git a/unsupported/crowdstrike-falcon/1.0.0/src/app.py b/unsupported/crowdstrike-falcon/1.0.0/src/app.py new file mode 100755 index 00000000..376d9ff3 --- /dev/null +++ b/unsupported/crowdstrike-falcon/1.0.0/src/app.py @@ -0,0 +1,3749 @@ +import requests +import asyncio +import json +import urllib3 + +from walkoff_app_sdk.app_base import AppBase + +class Crowdstrike_Falcon(AppBase): + + __version__ = "1.0" + app_name = "Crowdstrike_Falcon" + + + def __init__(self, redis, logger, console_logger=None): + self.verify = False + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + super().__init__(redis, logger, console_logger) + + + def setup_headers(self, headers): + request_headers={} + + if len(headers) > 0: + for header in headers.split("\n"): + if '=' in header: + headersplit=header.split('=') + request_headers[headersplit[0].strip()] = headersplit[1].strip() + elif ':' in header: + headersplit=header.split(':') + request_headers[headersplit[0].strip()] = headersplit[1].strip() + return request_headers + + + def setup_params(self, queries): + params={} + + if len(queries) > 0: + for query in queries.split("\&"): + if '=' in query: + headersplit=query.split('&') + params[headersplit[0].strip()] = headersplit[1].strip() + + return params + + + def generate_oauth2_access_token(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/oauth2/token" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + body={'client_id': client_id, 'client_secret': client_secret} + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def revoke_oauth2_access_token(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/oauth2/revoke" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + body={'client_id': client_id, 'client_secret': client_secret} + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def download_analysis_artifacts(self, url, client_id, client_secret, id, headers="", queries="", name=""): + params={} + request_headers={} + url=f"{url}/falconx/entities/artifacts/v1?id={id}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + + if name: + params["name"] = name + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_detect_aggregates(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={"Content-Type": "application/json","Accept": "application/json"} + url=f"{url}/detects/aggregates/detects/GET/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def view_information_about_detections(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={"Content-Type": "application/json","Accept": "application/json"} + url=f"{url}/detects/entities/summaries/GET/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def modify_detections(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={"Content-Type": "application/json","Accept": "application/json"} + url=f"{url}/detects/entities/detects/v2" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.patch(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_sandbox_reports(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): + params={} + request_headers={} + url=f"{url}/falconx/queries/reports/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_rules_by_id(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/ioarules/entities/rules/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def delete_rules_from_a_rule_group_by_id(self, url, client_id, client_secret, rule_group_id, ids, headers="", queries="", comment=""): + params={} + request_headers={} + url=f"{url}/ioarules/entities/rules/v1?rule_group_id={rule_group_id}&ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.delete(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def create_a_rule_within_a_rule_group(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={"Content-Type": "application/json","Accept": "application/json"} + url=f"{url}/ioarules/entities/rules/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def update_rules_within_a_rule_group(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={"Content-Type": "application/json","Accept": "application/json"} + url=f"{url}/ioarules/entities/rules/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.patch(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def search_for_prevention_policy_members(self, url, client_id, client_secret, headers="", queries="", id="", filter="", offset="", limit="", sort=""): + params={} + request_headers={} + url=f"{url}/policy/combined/prevention-members/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if filter: + params["filter"] = filter + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def set_precedence_of_device_control_policies(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/policy/entities/device-control-precedence/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def retrieve_hidden_hosts(self, url, client_id, client_secret, headers="", queries="", offset="", limit="", sort="", filter=""): + params={} + request_headers={} + url=f"{url}/devices/queries/devices-hidden/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + if filter: + params["filter"] = filter + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_rule_types_by_id(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/ioarules/entities/rule-types/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_all_platform_ids(self, url, client_id, client_secret, headers="", queries="", offset="", limit=""): + params={} + request_headers={} + url=f"{url}/ioarules/queries/platforms/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if limit: + params["limit"] = limit + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_combined_for_indicators(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): + params={} + request_headers={} + url=f"{url}/iocs/combined/indicator/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def set_precedence_of_response_policies(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/policy/entities/response-precedence/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_a_set_of_sensor_visibility_exclusions(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/policy/entities/sv-exclusions/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def delete_the_sensor_visibility_exclusions_by_id(self, url, client_id, client_secret, ids, headers="", queries="", comment=""): + params={} + request_headers={} + url=f"{url}/policy/entities/sv-exclusions/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.delete(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def create_the_sensor_visibility_exclusions(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/policy/entities/sv-exclusions/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def update_the_sensor_visibility_exclusions(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/policy/entities/sv-exclusions/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.patch(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def search_for_prevention_policy_ids(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): + params={} + request_headers={} + url=f"{url}/policy/queries/prevention/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_notifications_based_on_their_ids(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/recon/entities/notifications/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def delete_notifications_based_on_ids_notifications(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/recon/entities/notifications/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.delete(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def update_notification_status_or_assignee(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={"Content-Type": "application/json","Accept": "application/json"} + url=f"{url}/recon/entities/notifications/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.patch(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_sensor_installer_ids_by_provided_query(self, url, client_id, client_secret, headers="", queries="", offset="", limit="", sort="", filter=""): + params={} + request_headers={} + url=f"{url}/sensors/queries/installers/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + if filter: + params["filter"] = filter + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_info_about_indicators(self, url, client_id, client_secret, headers="", queries="", offset="", limit="", sort="", filter="", q="", include_deleted=""): + params={} + request_headers={} + url=f"{url}/intel/combined/indicators/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + if filter: + params["filter"] = filter + if q: + params["q"] = q + if include_deleted: + params["include_deleted"] = include_deleted + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def download_earlier_rule_sets(self, url, client_id, client_secret, id, headers="", queries="", format=""): + params={} + request_headers={"Accept": "undefined"} + url=f"{url}/intel/entities/rules-files/v1?id={id}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_report_ids(self, url, client_id, client_secret, headers="", queries="", offset="", limit="", sort="", filter="", q=""): + params={} + request_headers={} + url=f"{url}/intel/queries/reports/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + if filter: + params["filter"] = filter + if q: + params["q"] = q + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def search_for_rule_ids(self, url, client_id, client_secret, type, headers="", queries="", offset="", limit="", sort="", name="", description="", tags="", min_created_date="", max_created_date="", q=""): + params={} + request_headers={} + url=f"{url}/intel/queries/rules/v1?type={type}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + if name: + params["name"] = name + if description: + params["description"] = description + if tags: + params["tags"] = tags + if min_created_date: + params["min_created_date"] = min_created_date + if max_created_date: + params["max_created_date"] = max_created_date + if q: + params["q"] = q + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def search_for_sensor_update_policies(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): + params={} + request_headers={} + url=f"{url}/policy/combined/sensor-update/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_a_set_of_ioa_exclusions(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/policy/entities/ioa-exclusions/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def delete_the_ioa_exclusions_by_id(self, url, client_id, client_secret, ids, headers="", queries="", comment=""): + params={} + request_headers={} + url=f"{url}/policy/entities/ioa-exclusions/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.delete(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def create_the_ioa_exclusions(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/policy/entities/ioa-exclusions/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def update_the_ioa_exclusions(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/policy/entities/ioa-exclusions/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.patch(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def search_for_sensor_update_policy_member_ids(self, url, client_id, client_secret, headers="", queries="", id="", filter="", offset="", limit="", sort=""): + params={} + request_headers={} + url=f"{url}/policy/queries/sensor-update-members/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if filter: + params["filter"] = filter + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def search_for_sensor_visibility_exclusions(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): + params={} + request_headers={} + url=f"{url}/policy/queries/sv-exclusions/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def find_ids_for_submitted_scans(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): + params={} + request_headers={} + url=f"{url}/scanner/queries/scans/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_sensor_installer_details_by_provided_query(self, url, client_id, client_secret, headers="", queries="", offset="", limit="", sort="", filter=""): + params={} + request_headers={} + url=f"{url}/sensors/combined/installers/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + if filter: + params["filter"] = filter + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def search_for_hosts(self, url, client_id, client_secret, headers="", queries="", offset="", limit="", sort="", filter=""): + params={} + request_headers={} + url=f"{url}/devices/queries/devices-scroll/v1" + + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + if filter: + params["filter"] = filter + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_info_about_reports(self, url, client_id, client_secret, headers="", queries="", offset="", limit="", sort="", filter="", q="", fields=""): + params={} + request_headers={} + url=f"{url}/intel/combined/reports/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + if filter: + params["filter"] = filter + if q: + params["q"] = q + if fields: + params["fields"] = fields + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_a_zipped_sample(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/malquery/entities/samples-fetch/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def schedule_samples_for_download(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={"Content-Type": "application/json","Accept": "application/json"} + url=f"{url}/malquery/entities/samples-multidownload/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def perform_action_on_the_sensor_update_policies(self, url, client_id, client_secret, action_name, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/policy/entities/sensor-update-actions/v1?action_name={action_name}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def query_notifications(self, url, client_id, client_secret, headers="", queries="", offset="", limit="", sort="", filter="", q=""): + params={} + request_headers={} + url=f"{url}/recon/queries/notifications/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + if filter: + params["filter"] = filter + if q: + params["q"] = q + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def search_for_prevention_policies(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): + params={} + request_headers={} + url=f"{url}/policy/combined/prevention/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_status_of_an_executed_active_responder_command_on_a_single_host(self, url, client_id, client_secret, cloud_request_id, sequence_id, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/real-time-response/entities/active-responder-command/v1?cloud_request_id={cloud_request_id}&sequence_id={sequence_id}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def execute_an_active_responder_command_on_a_single_host(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/real-time-response/entities/active-responder-command/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def find_all_rule_ids(self, url, client_id, client_secret, headers="", queries="", sort="", filter="", q="", offset="", limit=""): + params={} + request_headers={} + url=f"{url}/ioarules/queries/rules/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if filter: + params["filter"] = filter + if q: + params["q"] = q + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def set_precedence_of_prevention_policies(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/policy/entities/prevention-precedence/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_indicators_ids(self, url, client_id, client_secret, headers="", queries="", offset="", limit="", sort="", filter="", q="", include_deleted=""): + params={} + request_headers={} + url=f"{url}/intel/queries/indicators/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + if filter: + params["filter"] = filter + if q: + params["q"] = q + if include_deleted: + params["include_deleted"] = include_deleted + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def search_for_sensor_update_policy_members(self, url, client_id, client_secret, headers="", queries="", id="", filter="", offset="", limit="", sort=""): + params={} + request_headers={} + url=f"{url}/policy/combined/sensor-update-members/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if filter: + params["filter"] = filter + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def batch_refresh_a_rtr_session_on_multiple_hosts_rtr_sessions_will_expire_after_10_minutes_unless_refreshed(self, url, client_id, client_secret, headers="", queries="", timeout="", timeout_duration="", body=""): + params={} + request_headers={} + url=f"{url}/real-time-response/combined/batch-refresh-session/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if timeout_duration: + params["timeout_duration"] = timeout_duration + body = " ".join(body.strip().split()).encode("utf-8") + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_queued_session_metadata_by_session_id(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/real-time-response/entities/queued-sessions/GET/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def perform_action_on_the_device_control_policies(self, url, client_id, client_secret, action_name, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/policy/entities/device-control-actions/v1?action_name={action_name}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_scans_aggregations(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={"Content-Type": "application/json","Accept": "application/json"} + url=f"{url}/scanner/aggregates/scans/GET/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_detailed_notifications_based_on_their_ids(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/recon/entities/notifications-detailed-translated/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def retrieve_specific_indicators_using_their_indicator_ids(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={"Content-Type": "application/json","Accept": "application/json"} + url=f"{url}/intel/entities/indicators/GET/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def find_all_rule_group_ids(self, url, client_id, client_secret, headers="", queries="", sort="", filter="", q="", offset="", limit=""): + params={} + request_headers={} + url=f"{url}/ioarules/queries/rule-groups/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if filter: + params["filter"] = filter + if q: + params["q"] = q + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def search_falcon_malquery(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={"Content-Type": "application/json","Accept": "application/json"} + url=f"{url}/malquery/queries/exact-search/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def retrieve_available_builds_for_use_with_sensor_update_policies(self, url, client_id, client_secret, headers="", queries="", platform=""): + params={} + request_headers={} + url=f"{url}/policy/combined/sensor-update-builds/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def search_for_firewall_policies(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): + params={} + request_headers={} + url=f"{url}/policy/queries/firewall/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def retrieve_set_of_host_groups(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/devices/entities/host-groups/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def delete_set_of_host_groups(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/devices/entities/host-groups/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.delete(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def create_host_groups(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/devices/entities/host-groups/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def update_host_groups(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/devices/entities/host-groups/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.patch(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def search_for_behaviors(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): + params={} + request_headers={} + url=f"{url}/incidents/queries/behaviors/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def search_for_incidents(self, url, client_id, client_secret, headers="", queries="", sort="", filter="", offset="", limit=""): + params={} + request_headers={} + url=f"{url}/incidents/queries/incidents/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if filter: + params["filter"] = filter + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_rule_groups_by_id(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/ioarules/entities/rule-groups/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def delete_rule_groups_by_id(self, url, client_id, client_secret, ids, headers="", queries="", comment=""): + params={} + request_headers={} + url=f"{url}/ioarules/entities/rule-groups/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.delete(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def create_a_rule_group(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={"Content-Type": "application/json","Accept": "application/json"} + url=f"{url}/ioarules/entities/rule-groups/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def update_a_rule_group(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={"Content-Type": "application/json","Accept": "application/json"} + url=f"{url}/ioarules/entities/rule-groups/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.patch(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_all_rule_type_ids(self, url, client_id, client_secret, headers="", queries="", offset="", limit=""): + params={} + request_headers={} + url=f"{url}/ioarules/queries/rule-types/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if limit: + params["limit"] = limit + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_information_about_search_and_download_quotas(self, url, client_id, client_secret, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/malquery/aggregates/quotas/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def refresh_a_session_timeout_on_a_single_host(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/real-time-response/entities/refresh-session/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def query_crowdscore(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): + params={} + request_headers={} + url=f"{url}/incidents/combined/crowdscores/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def perform_actions_on_incidents(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={"Content-Type": "application/json","Accept": "application/json"} + url=f"{url}/incidents/entities/incident-actions/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_info_about_actors(self, url, client_id, client_secret, headers="", queries="", offset="", limit="", sort="", filter="", q="", fields=""): + params={} + request_headers={} + url=f"{url}/intel/combined/actors/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + if filter: + params["filter"] = filter + if q: + params["q"] = q + if fields: + params["fields"] = fields + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def search_for_response_policy_members(self, url, client_id, client_secret, headers="", queries="", id="", filter="", offset="", limit="", sort=""): + params={} + request_headers={} + url=f"{url}/policy/combined/response-members/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if filter: + params["filter"] = filter + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def batch_initialize_a_rtr_session_on_multiple_hosts__before_any_rtr_commands_can_be_used_an_active_session_is_needed_on_the_host(self, url, client_id, client_secret, headers="", queries="", timeout="", timeout_duration="", body=""): + params={} + request_headers={} + url=f"{url}/real-time-response/combined/batch-init-session/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if timeout_duration: + params["timeout_duration"] = timeout_duration + body = " ".join(body.strip().split()).encode("utf-8") + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_rtr_extracted_file_contents_for_specified_session_and_sha256(self, url, client_id, client_secret, session_id, sha256, headers="", queries="", filename=""): + params={} + request_headers={} + url=f"{url}/real-time-response/entities/extracted-file-contents/v1?session_id={session_id}&sha256={sha256}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def search_for_host_groups(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): + params={} + request_headers={} + url=f"{url}/devices/combined/host-groups/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_all_pattern_severity_ids(self, url, client_id, client_secret, headers="", queries="", offset="", limit=""): + params={} + request_headers={} + url=f"{url}/ioarules/queries/pattern-severities/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if limit: + params["limit"] = limit + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_indicators_by_ids(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/iocs/entities/indicators/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def delete_indicators_by_ids(self, url, client_id, client_secret, headers="", queries="", filter="", ids="", comment=""): + params={} + request_headers={} + url=f"{url}/iocs/entities/indicators/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if ids: + params["ids"] = ids + if comment: + params["comment"] = comment + + ret = requests.delete(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def create_indicators(self, url, client_id, client_secret, headers="", queries="", retrodetects="", ignore_warnings="", body=""): + params={} + request_headers={"Content-Type": "application/json","Accept": "application/jsonX-CS-USERNAME"} + url=f"{url}/iocs/entities/indicators/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if ignore_warnings: + params["ignore_warnings"] = ignore_warnings + body = " ".join(body.strip().split()).encode("utf-8") + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def update_indicators(self, url, client_id, client_secret, headers="", queries="", retrodetects="", ignore_warnings="", body=""): + params={} + request_headers={"Content-Type": "application/json","Accept": "application/jsonX-CS-USERNAME"} + url=f"{url}/iocs/entities/indicators/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if ignore_warnings: + params["ignore_warnings"] = ignore_warnings + body = " ".join(body.strip().split()).encode("utf-8") + ret = requests.patch(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def retrieve_a_set_of_device_control_policies(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/policy/entities/device-control/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def delete_a_set_of_device_control_policies(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/policy/entities/device-control/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.delete(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def create_device_control_policies(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/policy/entities/device-control/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def update_device_control_policies(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/policy/entities/device-control/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.patch(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def search_for_ioa_exclusions(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): + params={} + request_headers={} + url=f"{url}/policy/queries/ioa-exclusions/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_aggregates_on_session_data(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/real-time-response/aggregates/sessions/GET/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def delete_a_session(self, url, client_id, client_secret, session_id, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/real-time-response/entities/sessions/v1?session_id={session_id}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.delete(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def initialize_a_new_session_with_the_rtr_cloud(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/real-time-response/entities/sessions/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_a_full_sandbox_report(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/falconx/entities/reports/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def delete_report(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/falconx/entities/reports/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.delete(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def search_for_ml_exclusions(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): + params={} + request_headers={} + url=f"{url}/policy/queries/ml-exclusions/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def search_for_sensor_update_policy_ids(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): + params={} + request_headers={} + url=f"{url}/policy/queries/sensor-update/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def delete_a_queued_session_command(self, url, client_id, client_secret, session_id, cloud_request_id, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/real-time-response/entities/queued-sessions/command/v1?session_id={session_id}&cloud_request_id={cloud_request_id}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.delete(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def preview_rules_notification_count_and_distribution(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={"X-CS-USERUUID": "undefined"} + url=f"{url}/recon/aggregates/rules-preview/GET/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_a_report_pdf_attachment(self, url, client_id, client_secret, id, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/intel/entities/report-files/v1?id={id}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def retrieve_a_set_of_prevention_policies(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/policy/entities/prevention/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def delete_a_set_of_prevention_policies(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/policy/entities/prevention/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.delete(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def create_prevention_policies(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/policy/entities/prevention/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def update_prevention_policies(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/policy/entities/prevention/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.patch(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_putfiles_based_on_the_ids_given(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/real-time-response/entities/put-files/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def delete_a_putfile_based_on_the_ids_given(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/real-time-response/entities/put-files/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.delete(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def upload_a_new_putfile_to_use_for_the_rtr_put_command(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/real-time-response/entities/put-files/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_a_list_of_session_ids(self, url, client_id, client_secret, headers="", queries="", offset="", limit="", sort="", filter=""): + params={} + request_headers={} + url=f"{url}/real-time-response/queries/sessions/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + if filter: + params["filter"] = filter + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def retrieve_list_of_samples(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={"Content-Type": "application/json","Accept": "application/jsonX-CS-USERUUID"} + url=f"{url}/samples/queries/samples/GET/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def check_status_of_sandbox_analysis(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/falconx/entities/submissions/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def submit_upload_for_sandbox_analysis(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/falconx/entities/submissions/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_number_of_hosts_that_have_observed_a_given_custom_ioc(self, url, client_id, client_secret, type, value, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/indicators/aggregates/devices-count/v1?type={type}&value={value}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def set_precedence_of_firewall_policies(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/policy/entities/firewall-precedence/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_notification_aggregates(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={"Content-Type": "application/json","Accept": "application/json"} + url=f"{url}/recon/aggregates/notifications/GET/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_actions_based_on_their_ids(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/recon/entities/actions/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def delete_an_action_from_a_monitoring_rule_based_on_the_action_id(self, url, client_id, client_secret, id, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/recon/entities/actions/v1?id={id}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.delete(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def create_actions_for_a_monitoring_rule(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={"Content-Type": "application/json","Accept": "application/json"} + url=f"{url}/recon/entities/actions/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def update_an_action_for_a_monitoring_rule(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={"Content-Type": "application/json","Accept": "application/json"} + url=f"{url}/recon/entities/actions/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.patch(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def query_actions(self, url, client_id, client_secret, headers="", queries="", offset="", limit="", sort="", filter="", q=""): + params={} + request_headers={} + url=f"{url}/recon/queries/actions/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + if filter: + params["filter"] = filter + if q: + params["q"] = q + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def search_for_host_group_ids(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): + params={} + request_headers={} + url=f"{url}/devices/queries/host-groups/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def retrieve_indexed_files_metadata_by_their_hash(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/malquery/entities/metadata/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def search_for_sensor_update_policies_with_additional_support_for_uninstall_protection(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): + params={} + request_headers={} + url=f"{url}/policy/combined/sensor-update/v2" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def perform_action_on_the_firewall_policies(self, url, client_id, client_secret, action_name, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/policy/entities/firewall-actions/v1?action_name={action_name}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_process_details(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/processes/entities/processes/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_a_short_summary_version_of_a_sandbox_report(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/falconx/entities/report-summaries/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def schedule_a_yara_based_search_for_execution(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={"Content-Type": "application/json","Accept": "application/json"} + url=f"{url}/malquery/queries/hunt/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def retrieve_the_status_of_batch_get_command__will_return_successful_files_when_they_are_finished_processing(self, url, client_id, client_secret, batch_get_cmd_req_id, headers="", queries="", timeout="", timeout_duration=""): + params={} + request_headers={} + url=f"{url}/real-time-response/combined/batch-get-command/v1?batch_get_cmd_req_id={batch_get_cmd_req_id}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if timeout_duration: + params["timeout_duration"] = timeout_duration + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def batch_executes_get_command_across_hosts_to_retrieve_files_after_this_call_is_made_get_realtimeresponsecombinedbatchgetcommandv1_is_used_to_query_for_the_results(self, url, client_id, client_secret, headers="", queries="", timeout="", timeout_duration="", body=""): + params={} + request_headers={} + url=f"{url}/real-time-response/combined/batch-get-command/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if timeout_duration: + params["timeout_duration"] = timeout_duration + body = " ".join(body.strip().split()).encode("utf-8") + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def query_monitoring_rules(self, url, client_id, client_secret, headers="", queries="", offset="", limit="", sort="", filter="", q=""): + params={} + request_headers={"X-CS-USERUUID": "undefined"} + url=f"{url}/recon/queries/rules/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + if filter: + params["filter"] = filter + if q: + params["q"] = q + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_sensor_installer_details_by_provided_sha256_ids(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/sensors/entities/installers/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def modify_host_tags(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={"Content-Type": "application/json","Accept": "application/json"} + url=f"{url}/devices/entities/devices/tags/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.patch(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def search_for_response_policy_member_ids(self, url, client_id, client_secret, headers="", queries="", id="", filter="", offset="", limit="", sort=""): + params={} + request_headers={} + url=f"{url}/policy/queries/response-members/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if filter: + params["filter"] = filter + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_status_of_an_executed_rtr_administrator_command_on_a_single_host(self, url, client_id, client_secret, cloud_request_id, sequence_id, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/real-time-response/entities/admin-command/v1?cloud_request_id={cloud_request_id}&sequence_id={sequence_id}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def execute_a_rtr_administrator_command_on_a_single_host(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/real-time-response/entities/admin-command/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def refresh_an_active_event_stream(self, url, client_id, client_secret, action_name, appId, partition, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/sensors/entities/datafeed-actions/v1/{partition}?action_name={action_name}&appId={appId}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def validates_field_values_and_checks_for_string_matches(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={"Content-Type": "application/json","Accept": "application/json"} + url=f"{url}/ioarules/entities/rules/validate/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def check_the_status_of_a_volume_scan(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/scanner/entities/scans/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def submit_a_volume_of_files_for_ml_scanning(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/scanner/entities/scans/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def download_the_latest_rule_set(self, url, client_id, client_secret, type, headers="", queries="", format=""): + params={} + request_headers={"Accept": "undefined"} + url=f"{url}/intel/entities/rules-latest-files/v1?type={type}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_rules_by_id(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={"Content-Type": "application/json","Accept": "application/json"} + url=f"{url}/ioarules/entities/rules/GET/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def find_all_rule_groups(self, url, client_id, client_secret, headers="", queries="", sort="", filter="", q="", offset="", limit=""): + params={} + request_headers={} + url=f"{url}/ioarules/queries/rule-groups-full/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if filter: + params["filter"] = filter + if q: + params["q"] = q + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def check_the_status_and_results_of_an_asynchronous_request(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/malquery/entities/requests/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_a_set_of_ml_exclusions(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/policy/entities/ml-exclusions/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def delete_the_ml_exclusions_by_id(self, url, client_id, client_secret, ids, headers="", queries="", comment=""): + params={} + request_headers={} + url=f"{url}/policy/entities/ml-exclusions/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.delete(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def create_the_ml_exclusions(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/policy/entities/ml-exclusions/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def update_the_ml_exclusions(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/policy/entities/ml-exclusions/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.patch(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def search_for_device_control_policy_ids(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): + params={} + request_headers={} + url=f"{url}/policy/queries/device-control/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def search_for_firewall_policy_member_ids(self, url, client_id, client_secret, headers="", queries="", id="", filter="", offset="", limit="", sort=""): + params={} + request_headers={} + url=f"{url}/policy/queries/firewall-members/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if filter: + params["filter"] = filter + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_notifications_based_on_their_ids(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/recon/entities/notifications-translated/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def search_for_host_group_members(self, url, client_id, client_secret, headers="", queries="", id="", filter="", offset="", limit="", sort=""): + params={} + request_headers={} + url=f"{url}/devices/combined/host-group-members/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if filter: + params["filter"] = filter + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_platforms_by_id(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/ioarules/entities/platforms/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def perform_action_on_the_response_policies(self, url, client_id, client_secret, action_name, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/policy/entities/response-actions/v1?action_name={action_name}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def retrieve_a_set_of_response_policies(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/policy/entities/response/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def delete_a_set_of_response_policies(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/policy/entities/response/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.delete(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def create_response_policies(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/policy/entities/response/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def update_response_policies(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/policy/entities/response/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.patch(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def batch_executes_a_rtr_readonly_command(self, url, client_id, client_secret, headers="", queries="", timeout="", timeout_duration="", body=""): + params={} + request_headers={} + url=f"{url}/real-time-response/combined/batch-command/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if timeout_duration: + params["timeout_duration"] = timeout_duration + body = " ".join(body.strip().split()).encode("utf-8") + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_session_metadata_by_session_id(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/real-time-response/entities/sessions/GET/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def perform_action_on_host_group(self, url, client_id, client_secret, action_name, host_group_id, hostnames, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/devices/entities/host-group-actions/v1?action_name={action_name}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + body = {"action_parameters": [{"name": "filter", "value": "(hostname:['" + hostnames + "'])" } ], "ids": [ host_group_id ]} + ret = requests.post(url, headers=request_headers, params=params, json=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def search_for_device_control_policy_members(self, url, client_id, client_secret, headers="", queries="", id="", filter="", offset="", limit="", sort=""): + params={} + request_headers={} + url=f"{url}/policy/combined/device-control-members/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if filter: + params["filter"] = filter + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def search_for_firewall_policies(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): + params={} + request_headers={} + url=f"{url}/policy/combined/firewall/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def retrieve_a_set_of_sensor_update_policies_with_additional_support_for_uninstall_protection(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/policy/entities/sensor-update/v2?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def create_sensor_update_policies(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/policy/entities/sensor-update/v2" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def update_sensor_update_policies(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/policy/entities/sensor-update/v2" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.patch(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_a_list_of_putfile_ids(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): + params={} + request_headers={} + url=f"{url}/real-time-response/queries/put-files/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_a_list_of_custom_script_ids(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): + params={} + request_headers={} + url=f"{url}/real-time-response/queries/scripts/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_detailed_notifications_based_on_their_ids_with_raw_intelligence_content_that_generated_the_match(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/recon/entities/notifications-detailed/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_all_event_streams(self, url, client_id, client_secret, appId, headers="", queries="", format=""): + params={} + request_headers={} + url=f"{url}/sensors/entities/datafeed/v2?appId={appId}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def download_sensor_installer_by_sha256_id(self, url, client_id, client_secret, id, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/sensors/entities/download-installer/v1?id={id}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_hosts_that_have_observed_a_given_custom_ioc(self, url, client_id, client_secret, type, value, headers="", queries="", limit="", offset=""): + params={} + request_headers={} + url=f"{url}/indicators/queries/devices/v1?type={type}&value={value}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if offset: + params["offset"] = offset + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def retrieve_details_for_rule_sets_for_ids(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/intel/entities/rules/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def download_a_file_indexed_by_malquery(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/malquery/entities/download-files/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def retrieve_an_uninstall_token_for_a_specific_device(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/policy/combined/reveal-uninstall-token/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def search_for_response_policy_ids(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): + params={} + request_headers={} + url=f"{url}/policy/queries/response/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_a_list_of_files_for_rtr_session(self, url, client_id, client_secret, session_id, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/real-time-response/entities/file/v1?session_id={session_id}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def delete_a_rtr_session_file(self, url, client_id, client_secret, ids, session_id, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/real-time-response/entities/file/v1?ids={ids}&session_id={session_id}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.delete(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_custom_scripts_based_on_the_ids_given(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/real-time-response/entities/scripts/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def delete_a_custom_script_based_on_the_id_given(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/real-time-response/entities/scripts/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.delete(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def upload_a_new_custom_script_to_use(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/real-time-response/entities/scripts/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def upload_a_new_scripts_to_replace_an_existing_one(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/real-time-response/entities/scripts/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.patch(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_details_on_hosts(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/devices/entities/devices/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_actor_ids(self, url, client_id, client_secret, headers="", queries="", offset="", limit="", sort="", filter="", q=""): + params={} + request_headers={} + url=f"{url}/intel/queries/actors/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + if filter: + params["filter"] = filter + if q: + params["q"] = q + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_ccid_to_use_with_sensor_installers(self, url, client_id, client_secret, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/sensors/queries/installers/ccid/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def find_submission_ids_for_uploaded_files(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): + params={} + request_headers={} + url=f"{url}/falconx/queries/submissions/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_details_on_behaviors(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={"Content-Type": "application/json","Accept": "application/json"} + url=f"{url}/incidents/entities/behaviors/GET/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def search_for_device_control_policies(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): + params={} + request_headers={} + url=f"{url}/policy/combined/device-control/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def search_for_prevention_policy_member_ids(self, url, client_id, client_secret, headers="", queries="", id="", filter="", offset="", limit="", sort=""): + params={} + request_headers={} + url=f"{url}/policy/queries/prevention-members/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if filter: + params["filter"] = filter + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_status_of_an_executed_command_on_a_single_host(self, url, client_id, client_secret, cloud_request_id, sequence_id, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/real-time-response/entities/command/v1?cloud_request_id={cloud_request_id}&sequence_id={sequence_id}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def execute_a_command_on_a_single_host(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/real-time-response/entities/command/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def retrieve_the_file_associated_with_the_given_id_sha256(self, url, client_id, client_secret, ids, headers="", queries="", password_protected=""): + params={} + request_headers={"X-CS-USERUUID": "undefined"} + url=f"{url}/samples/entities/samples/v3?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def delete_sample_from_the_collection(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={"X-CS-USERUUID": "undefined"} + url=f"{url}/samples/entities/samples/v3?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.delete(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def upload_a_file_for_further_cloud_analysis(self, url, client_id, client_secret, file_name, headers="", queries="", comment="", is_confidential="", body=""): + params={} + request_headers={"X-CS-USERUUID": "undefined"} + url=f"{url}/samples/entities/samples/v3?file_name={file_name}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if is_confidential: + params["is_confidential"] = is_confidential + body = " ".join(body.strip().split()).encode("utf-8") + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def search_for_response_policies(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): + params={} + request_headers={} + url=f"{url}/policy/combined/response/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def retrieve_a_set_of_firewall_policies(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/policy/entities/firewall/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def delete_a_set_of_firewall_policies(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/policy/entities/firewall/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.delete(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def create_firewall_policies(self, url, client_id, client_secret, headers="", queries="", clone_id="", body=""): + params={} + request_headers={} + url=f"{url}/policy/entities/firewall/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + body = " ".join(body.strip().split()).encode("utf-8") + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def update_firewall_policies(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/policy/entities/firewall/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.patch(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def set_precedence_of_sensor_update_policies(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/policy/entities/sensor-update-precedence/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def search_for_device_control_policy_member_ids(self, url, client_id, client_secret, headers="", queries="", id="", filter="", offset="", limit="", sort=""): + params={} + request_headers={} + url=f"{url}/policy/queries/device-control-members/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if filter: + params["filter"] = filter + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def batch_executes_a_rtr_active_responder_command(self, url, client_id, client_secret, headers="", queries="", timeout="", timeout_duration="", body=""): + params={} + request_headers={} + url=f"{url}/real-time-response/combined/batch-active-responder-command/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if timeout_duration: + params["timeout_duration"] = timeout_duration + body = " ".join(body.strip().split()).encode("utf-8") + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def batch_executes_a_rtr_administrator_command(self, url, client_id, client_secret, headers="", queries="", timeout="", timeout_duration="", body=""): + params={} + request_headers={} + url=f"{url}/real-time-response/combined/batch-admin-command/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if timeout_duration: + params["timeout_duration"] = timeout_duration + body = " ".join(body.strip().split()).encode("utf-8") + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_monitoring_rules_rules_by_provided_ids(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={"X-CS-USERUUID": "undefined"} + url=f"{url}/recon/entities/rules/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def delete_monitoring_rules(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={"X-CS-USERUUID": "undefined"} + url=f"{url}/recon/entities/rules/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.delete(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def create_monitoring_rules(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={"X-CS-USERUUID": "undefined"} + url=f"{url}/recon/entities/rules/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def update_monitoring_rules(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={"X-CS-USERUUID": "undefined"} + url=f"{url}/recon/entities/rules/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.patch(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def search_for_detection_ids(self, url, client_id, client_secret, headers="", queries="", offset="", limit="", sort="", filter="", q=""): + params={} + request_headers={} + url=f"{url}/detects/queries/detects/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + if filter: + params["filter"] = filter + if q: + params["q"] = q + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def retrieve_the_file_associated_with_the_given_id_sha256(self, url, client_id, client_secret, ids, headers="", queries="", password_protected=""): + params={} + request_headers={"X-CS-USERUUID": "undefined"} + url=f"{url}/samples/entities/samples/v2?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def upload_for_sandbox_analysis(self, url, client_id, client_secret, file_name, headers="", queries="", comment="", is_confidential="", body=""): + params={} + request_headers={"X-CS-USERUUID": "undefined"} + url=f"{url}/samples/entities/samples/v2?file_name={file_name}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if is_confidential: + params["is_confidential"] = is_confidential + body = " ".join(body.strip().split()).encode("utf-8") + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def search_for_host_group_member_ids(self, url, client_id, client_secret, headers="", queries="", id="", filter="", offset="", limit="", sort=""): + params={} + request_headers={} + url=f"{url}/devices/queries/host-group-members/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if filter: + params["filter"] = filter + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_details_on_incidents(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={"Content-Type": "application/json","Accept": "application/json"} + url=f"{url}/incidents/entities/incidents/GET/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def search_for_processes_associated_with_a_custom_ioc(self, url, client_id, client_secret, type, value, device_id, headers="", queries="", limit="", offset=""): + params={} + request_headers={} + url=f"{url}/indicators/queries/processes/v1?type={type}&value={value}&device_id={device_id}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if offset: + params["offset"] = offset + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def retrieve_specific_reports_using_their_report_ids(self, url, client_id, client_secret, ids, headers="", queries="", fields=""): + params={} + request_headers={} + url=f"{url}/intel/entities/reports/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def search_for_indicators(self, url, client_id, client_secret, headers="", queries="", filter="", offset="", limit="", sort=""): + params={} + request_headers={} + url=f"{url}/iocs/queries/indicators/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def search_for_firewall_policy_members(self, url, client_id, client_secret, headers="", queries="", id="", filter="", offset="", limit="", sort=""): + params={} + request_headers={} + url=f"{url}/policy/combined/firewall-members/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if filter: + params["filter"] = filter + if offset: + params["offset"] = offset + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def perform_action_on_the_prevention_policies(self, url, client_id, client_secret, action_name, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/policy/entities/prevention-actions/v1?action_name={action_name}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def retrieve_a_set_of_sensor_update_policies(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/policy/entities/sensor-update/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def delete_a_set_of_sensor_update_policies(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/policy/entities/sensor-update/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.delete(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def create_sensor_update_policies(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/policy/entities/sensor-update/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def update_sensor_update_policies(self, url, client_id, client_secret, headers="", queries="", body=""): + params={} + request_headers={} + url=f"{url}/policy/entities/sensor-update/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.patch(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def take_action_on_hosts(self, url, client_id, client_secret, action_name, headers="", queries="", body=""): + params={} + request_headers={"Content-Type": "application/json","Accept": "application/json"} + url=f"{url}/devices/entities/devices-actions/v2?action_name={action_name}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.post(url, headers=request_headers, params=params, data=body) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def search_for_hosts(self, url, client_id, client_secret, headers="", queries="", offset="", limit="", sort="", filter=""): + params={} + request_headers={} + url=f"{url}/devices/queries/devices/v1" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + if limit: + params["limit"] = limit + if sort: + params["sort"] = sort + if filter: + params["filter"] = filter + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def retrieve_specific_actors_using_their_actor_ids(self, url, client_id, client_secret, ids, headers="", queries="", fields=""): + params={} + request_headers={} + url=f"{url}/intel/entities/actors/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + + def get_pattern_severities_by_id(self, url, client_id, client_secret, ids, headers="", queries=""): + params={} + request_headers={} + url=f"{url}/ioarules/entities/pattern-severities/v1?ids={ids}" + request_headers=self.setup_headers(headers) + params=self.setup_params(queries) + + ret = requests.get(url, headers=request_headers, params=params) + try: + return ret.json() + except json.decoder.JSONDecodeError: + return ret.text + + +if __name__ == "__main__": + + Crowdstrike_Falcon.run() diff --git a/cylance/1.0.0/Dockerfile b/unsupported/cylance/1.0.0/Dockerfile similarity index 100% rename from cylance/1.0.0/Dockerfile rename to unsupported/cylance/1.0.0/Dockerfile diff --git a/cylance/1.0.0/api.yaml b/unsupported/cylance/1.0.0/api.yaml similarity index 100% rename from cylance/1.0.0/api.yaml rename to unsupported/cylance/1.0.0/api.yaml diff --git a/cylance/1.0.0/requirements.txt b/unsupported/cylance/1.0.0/requirements.txt similarity index 100% rename from cylance/1.0.0/requirements.txt rename to unsupported/cylance/1.0.0/requirements.txt diff --git a/cylance/1.0.0/src/app.py b/unsupported/cylance/1.0.0/src/app.py similarity index 100% rename from cylance/1.0.0/src/app.py rename to unsupported/cylance/1.0.0/src/app.py diff --git a/hoxhunt/1.0.0/Dockerfile b/unsupported/hoxhunt/1.0.0/Dockerfile similarity index 100% rename from hoxhunt/1.0.0/Dockerfile rename to unsupported/hoxhunt/1.0.0/Dockerfile diff --git a/hoxhunt/1.0.0/api.yaml b/unsupported/hoxhunt/1.0.0/api.yaml similarity index 100% rename from hoxhunt/1.0.0/api.yaml rename to unsupported/hoxhunt/1.0.0/api.yaml diff --git a/hoxhunt/1.0.0/requirements.txt b/unsupported/hoxhunt/1.0.0/requirements.txt similarity index 100% rename from hoxhunt/1.0.0/requirements.txt rename to unsupported/hoxhunt/1.0.0/requirements.txt diff --git a/hoxhunt/1.0.0/src/app.py b/unsupported/hoxhunt/1.0.0/src/app.py similarity index 100% rename from hoxhunt/1.0.0/src/app.py rename to unsupported/hoxhunt/1.0.0/src/app.py diff --git a/microsoft-identity-and-access/1.0.0/Dockerfile b/unsupported/microsoft-identity-and-access/1.0.0/Dockerfile similarity index 100% rename from microsoft-identity-and-access/1.0.0/Dockerfile rename to unsupported/microsoft-identity-and-access/1.0.0/Dockerfile diff --git a/microsoft-identity-and-access/1.0.0/README.md b/unsupported/microsoft-identity-and-access/1.0.0/README.md similarity index 100% rename from microsoft-identity-and-access/1.0.0/README.md rename to unsupported/microsoft-identity-and-access/1.0.0/README.md diff --git a/microsoft-identity-and-access/1.0.0/api.yaml b/unsupported/microsoft-identity-and-access/1.0.0/api.yaml similarity index 100% rename from microsoft-identity-and-access/1.0.0/api.yaml rename to unsupported/microsoft-identity-and-access/1.0.0/api.yaml diff --git a/microsoft-identity-and-access/1.0.0/requirements.txt b/unsupported/microsoft-identity-and-access/1.0.0/requirements.txt similarity index 100% rename from microsoft-identity-and-access/1.0.0/requirements.txt rename to unsupported/microsoft-identity-and-access/1.0.0/requirements.txt diff --git a/microsoft-identity-and-access/1.0.0/src/app.py b/unsupported/microsoft-identity-and-access/1.0.0/src/app.py similarity index 100% rename from microsoft-identity-and-access/1.0.0/src/app.py rename to unsupported/microsoft-identity-and-access/1.0.0/src/app.py diff --git a/microsoft-intune/1.0.0/Dockerfile b/unsupported/microsoft-intune/1.0.0/Dockerfile similarity index 100% rename from microsoft-intune/1.0.0/Dockerfile rename to unsupported/microsoft-intune/1.0.0/Dockerfile diff --git a/microsoft-intune/1.0.0/README.md b/unsupported/microsoft-intune/1.0.0/README.md similarity index 100% rename from microsoft-intune/1.0.0/README.md rename to unsupported/microsoft-intune/1.0.0/README.md diff --git a/microsoft-intune/1.0.0/api.yaml b/unsupported/microsoft-intune/1.0.0/api.yaml similarity index 100% rename from microsoft-intune/1.0.0/api.yaml rename to unsupported/microsoft-intune/1.0.0/api.yaml diff --git a/microsoft-intune/1.0.0/requirements.txt b/unsupported/microsoft-intune/1.0.0/requirements.txt similarity index 100% rename from microsoft-intune/1.0.0/requirements.txt rename to unsupported/microsoft-intune/1.0.0/requirements.txt diff --git a/microsoft-intune/1.0.0/src/app.py b/unsupported/microsoft-intune/1.0.0/src/app.py similarity index 100% rename from microsoft-intune/1.0.0/src/app.py rename to unsupported/microsoft-intune/1.0.0/src/app.py diff --git a/microsoft-security-and-compliance/1.0.0/Dockerfile b/unsupported/microsoft-security-and-compliance/1.0.0/Dockerfile similarity index 100% rename from microsoft-security-and-compliance/1.0.0/Dockerfile rename to unsupported/microsoft-security-and-compliance/1.0.0/Dockerfile diff --git a/microsoft-security-and-compliance/1.0.0/README.md b/unsupported/microsoft-security-and-compliance/1.0.0/README.md similarity index 100% rename from microsoft-security-and-compliance/1.0.0/README.md rename to unsupported/microsoft-security-and-compliance/1.0.0/README.md diff --git a/microsoft-security-and-compliance/1.0.0/api.yaml b/unsupported/microsoft-security-and-compliance/1.0.0/api.yaml similarity index 100% rename from microsoft-security-and-compliance/1.0.0/api.yaml rename to unsupported/microsoft-security-and-compliance/1.0.0/api.yaml diff --git a/microsoft-security-and-compliance/1.0.0/requirements.txt b/unsupported/microsoft-security-and-compliance/1.0.0/requirements.txt similarity index 100% rename from microsoft-security-and-compliance/1.0.0/requirements.txt rename to unsupported/microsoft-security-and-compliance/1.0.0/requirements.txt diff --git a/microsoft-security-and-compliance/1.0.0/src/app.py b/unsupported/microsoft-security-and-compliance/1.0.0/src/app.py similarity index 100% rename from microsoft-security-and-compliance/1.0.0/src/app.py rename to unsupported/microsoft-security-and-compliance/1.0.0/src/app.py diff --git a/microsoft-security-oauth2/1.0.0/Dockerfile b/unsupported/microsoft-security-oauth2/1.0.0/Dockerfile similarity index 100% rename from microsoft-security-oauth2/1.0.0/Dockerfile rename to unsupported/microsoft-security-oauth2/1.0.0/Dockerfile diff --git a/microsoft-security-oauth2/1.0.0/api.yaml b/unsupported/microsoft-security-oauth2/1.0.0/api.yaml similarity index 100% rename from microsoft-security-oauth2/1.0.0/api.yaml rename to unsupported/microsoft-security-oauth2/1.0.0/api.yaml diff --git a/microsoft-security-oauth2/1.0.0/requirements.txt b/unsupported/microsoft-security-oauth2/1.0.0/requirements.txt similarity index 100% rename from microsoft-security-oauth2/1.0.0/requirements.txt rename to unsupported/microsoft-security-oauth2/1.0.0/requirements.txt diff --git a/microsoft-security-oauth2/1.0.0/src/app.py b/unsupported/microsoft-security-oauth2/1.0.0/src/app.py similarity index 100% rename from microsoft-security-oauth2/1.0.0/src/app.py rename to unsupported/microsoft-security-oauth2/1.0.0/src/app.py diff --git a/passivetotal/1.0.0/Dockerfile b/unsupported/microsoft-teams-system-access/1.0.0/Dockerfile similarity index 100% rename from passivetotal/1.0.0/Dockerfile rename to unsupported/microsoft-teams-system-access/1.0.0/Dockerfile diff --git a/unsupported/microsoft-teams-system-access/1.0.0/README.md b/unsupported/microsoft-teams-system-access/1.0.0/README.md new file mode 100644 index 00000000..4ef89c3d --- /dev/null +++ b/unsupported/microsoft-teams-system-access/1.0.0/README.md @@ -0,0 +1,33 @@ +## Microsoft Security and Compliance +- An app to interact with Security and Compliance solutions from microsoft. + +## How to register app in Active Directory on Azure portal ? + +### Step 1: Go to the Azure portal + + - You'll need to go to the [Azure Portal](https://portal.azure.com/) and login. + +### Step 2: Go to the Azure Active Directory Service + +- Once you are logged into Azure, Register a new application so you can access +the Microsoft Graph API. To register a new application go to your **Azure Active Directory** +and once there go down to **App Registrations** a new window will pop up. + +### Step 3: Register a New App +- Set name of your choice. +- Select supported account type. +- You don't have to set redirect URL. + +### Step 4: Generate client secret +- Go to your application → Certificates & Secrets → New client Secret. + +## Note +- You'll need Tenant ID, Client ID & client Secret for authentication (Tenant ID & Client ID are available under application overview and for Client Secret go to Certificate & Secrets section). +- Make sure your application has adequate permissions. +- Each action may require different permission to run. To add permissions, Go to your application in azure portal → API permission → Add permission (some of the permissions will require admin consent). +- After adding permission , Grant consent. +- Be sure to use work / business account. Most of the actions are not supported on personal account. + + +## References +- To read more about required permission for each action you can refer to [Security](https://docs.microsoft.com/en-us/graph/api/resources/security-api-overview?view=graph-rest-1.0) & [compliance](https://docs.microsoft.com/en-us/graph/api/resources/complianceapioverview?view=graph-rest-beta)'s official documentation. diff --git a/unsupported/microsoft-teams-system-access/1.0.0/api.yaml b/unsupported/microsoft-teams-system-access/1.0.0/api.yaml new file mode 100644 index 00000000..857fc2a1 --- /dev/null +++ b/unsupported/microsoft-teams-system-access/1.0.0/api.yaml @@ -0,0 +1,190 @@ +app_version: 1.0.0 +name: Microsoft Teams System Access +description: An app for the Microsoft teams WITHOUT delegated access +contact_info: + name: "@frikkylikeme" + url: https://frikky.com + email: frikky@shuffler.io +tags: + - Communication + - Comms + - Chat +categories: + - Comms +authentication: + required: true + parameters: + - name: tenant_id + description: The tenant of the OAuth client + example: "*****" + required: true + schema: + type: string + - name: client_id + description: The client id to use + example: "*****" + multiline: false + required: true + schema: + type: string + - name: client_secret + description: The secret key to use + multiline: false + example: "*****" + required: true + schema: + type: string +actions: + - name: list_teams + description: Returns all teams for a user + parameters: + - name: user_id + description: The user to check for + example: "b6b6c99f-bf87-4815-9f62-82aef893c634" + required: true + schema: + type: string + - name: list_members_in_team + description: Returns all members in a team + parameters: + - name: team_id + description: The team to check + example: "b6b6c99f-bf87-4815-9f62-82aef893c634" + required: true + schema: + type: string + - name: list_channels_in_team + description: Returns all channels for a team + parameters: + - name: team_id + description: The user to check for + example: "b6b6c99f-bf87-4815-9f62-82aef893c634" + required: true + schema: + type: string + - name: create_channel_in_team + description: Creates a channel in a team + parameters: + - name: team_id + description: The user to check for + example: "b6b6c99f-bf87-4815-9f62-82aef893c634" + required: true + schema: + type: string + - name: name + description: Add person to channel + example: "The coolest channel" + required: true + schema: + type: string + - name: description + description: The description to use for the channel + example: "And it really is only for cool people" + required: true + schema: + type: string + - name: add_user_to_channel + description: Adds a user to a channel + parameters: + - name: team_id + description: The user to check for + example: "b6b6c99f-bf87-4815-9f62-82aef893c634" + required: true + schema: + type: string + - name: channel_id + description: The channel ID to use + example: "b6b6c99f-bf87-4815-9f62-82aef893c634" + required: true + schema: + type: string + - name: user_id + description: The user to add + example: "b6b6c99f-bf87-4815-9f62-82aef893c634" + required: true + schema: + type: string + - name: role + description: The role to give them + required: true + options: + - member + - owner + schema: + type: string + #- name: send_message_to_channel + # description: Sends a message to a channel + # parameters: + # - name: team_id + # description: The user to check for + # example: "b6b6c99f-bf87-4815-9f62-82aef893c634" + # required: true + # schema: + # type: string + # - name: channel_id + # description: The channel ID to use + # example: "b6b6c99f-bf87-4815-9f62-82aef893c634" + # required: true + # schema: + # type: string + # - name: user_id + # description: The user ID to use + # example: "b6b6c99f-bf87-4815-9f62-82aef893c634" + # required: true + # schema: + # type: string + # - name: message + # description: The message to send + # example: "Have a nice weekend!!" + # required: true + # schema: + # type: string + - name: list_apps_in_team + description: Deletes a channel from a team + parameters: + - name: team_id + description: The user to check for + example: "b6b6c99f-bf87-4815-9f62-82aef893c634" + required: true + schema: + type: string + - name: get_app_in_team + description: Gets and app installed in a team + parameters: + - name: team_id + description: The user to check for + example: "b6b6c99f-bf87-4815-9f62-82aef893c634" + required: true + schema: + type: string + - name: app_id + description: The app ID to use + example: "b6b6c99f-bf87-4815-9f62-82aef893c634" + required: true + schema: + type: string + - name: add_webhook_to_team + description: Adds a webhook to a team + parameters: + - name: team_id + description: The user to check for + example: "b6b6c99f-bf87-4815-9f62-82aef893c634" + required: true + schema: + type: string + - name: delete_channel + description: Deletes a channel from a team + parameters: + - name: team_id + description: The user to check for + example: "b6b6c99f-bf87-4815-9f62-82aef893c634" + required: true + schema: + type: string + - name: channel_id + description: The channel ID to use + example: "b6b6c99f-bf87-4815-9f62-82aef893c634" + required: true + schema: + type: string +large_image: data:image/png;base64,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 diff --git a/passivetotal/1.0.0/requirements.txt b/unsupported/microsoft-teams-system-access/1.0.0/requirements.txt similarity index 100% rename from passivetotal/1.0.0/requirements.txt rename to unsupported/microsoft-teams-system-access/1.0.0/requirements.txt diff --git a/unsupported/microsoft-teams-system-access/1.0.0/src/app.py b/unsupported/microsoft-teams-system-access/1.0.0/src/app.py new file mode 100644 index 00000000..9ce1eee1 --- /dev/null +++ b/unsupported/microsoft-teams-system-access/1.0.0/src/app.py @@ -0,0 +1,275 @@ +import socket +import asyncio +import time +import random +import json +import uuid +import time +import requests + +from walkoff_app_sdk.app_base import AppBase + +# Antispam +# https://protection.office.com/threatpolicy +# https://protection.office.com/antispam +# https://docs.microsoft.com/en-us/microsoft-365/security/office-365-security/configure-the-connection-filter-policy?view=o365-worldwide + +#create_url = "https://compliance.microsoft.com/api/ComplianceSearch" +#Request URL: +# https://docs.microsoft.com/en-us/information-protection/develop/overview +# https://docs.microsoft.com/en-us/graph/api/resources/ediscovery-ediscoveryapioverview?view=graph-rest-beta +# Microsoft Graph Security securityAction entity +# https://docs.microsoft.com/en-us/graph/api/resources/threatassessment-api-overview?view=graph-rest-1.0 + +# Permissions (Delegated): SecurityEvents, ThreatAssement, ThreatIndicators, Compliance +# !! Have a "report email" internally using office365 !! +# Microsoft Threat Protection +# https://security.microsoft.com/mtp/ +# https://protection.office.com/api/AcceptedDomain + +class Teams(AppBase): + __version__ = "1.0.0" + app_name = "Teams" + + def __init__(self, redis, logger, console_logger=None): + """ + Each app should have this __init__ to set up Redis and logging. + :param redis: + :param logger: + :param console_logger: + """ + super().__init__(redis, logger, console_logger) + self.graph_url = "https://graph.microsoft.com" + + def authenticate(self, tenant_id, client_id, client_secret): + s = requests.Session() + auth_url = f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token" + auth_data = { + "grant_type": "client_credentials", + "client_id": client_id, + "client_secret": client_secret, + "scope": f"{self.graph_url}/.default", + } + auth_headers = { + "Content-Type": "application/x-www-form-urlencoded", + "cache-control": "no-cache", + } + + print(f"Making request to: {auth_url}") + res = s.post(auth_url, data=auth_data, headers=auth_headers) + + # Auth failed, raise exception with the response + if res.status_code != 200: + raise ConnectionError(res.text) + + access_token = res.json().get("access_token") + s.headers = {"Authorization": f"Bearer {access_token}", "cache-control": "no-cache"} + print(s) + return s + + # ENABLE: https://protection.office.com/api/OrganizationCustomization/Enable?source=HostedContentFilterPolicy + + def list_teams(self, tenant_id, client_id, client_secret, user_id): + session = self.authenticate(tenant_id, client_id, client_secret) + graph_url = "%s/v1.0/users/%s/joinedTeams" % (self.graph_url, user_id) + + ret = session.get(graph_url) + print(ret.status_code) + print(ret.text) + try: + data = ret.json() + except: + data = ret.text + + if ret.status_code < 300: + return {"success": True, "value": data} + + return {"success": False, "reason": "Bad status code %d - expecting 200." % ret.status_code, "graph_url": graph_url, "details": data} + + def list_members_in_team(self, tenant_id, client_id, client_secret, team_id): + session = self.authenticate(tenant_id, client_id, client_secret) + graph_url = "%s/v1.0/teams/%s/members" % (self.graph_url, team_id) + + ret = session.get(graph_url) + print(ret.status_code) + print(ret.text) + try: + data = ret.json() + except: + data = ret.text + + if ret.status_code < 300: + return {"success": True, "value": data} + + return {"success": False, "reason": "Bad status code %d - expecting 200." % ret.status_code, "url": graph_url, "details": data} + + def list_channels_in_team(self, tenant_id, client_id, client_secret, team_id): + session = self.authenticate(tenant_id, client_id, client_secret) + graph_url = "%s/v1.0/teams/%s/channels" % (self.graph_url, team_id) + + ret = session.get(graph_url) + print(ret.status_code) + print(ret.text) + try: + data = ret.json() + except: + data = ret.text + + if ret.status_code < 300: + return {"success": True, "value": data} + + return {"success": False, "reason": "Bad status code %d - expecting 200." % ret.status_code, "url": graph_url, "details": data} + + def add_user_to_channel(self, tenant_id, client_id, client_secret, team_id, channel_id, user_id, role): + session = self.authenticate(tenant_id, client_id, client_secret) + graph_url = "%s/v1.0/teams/%s/channels/%s/members" % (self.graph_url, team_id, channel_id) + + data = { + "@odata.type": "#microsoft.graph.aadUserConversationMember", + "roles": [role], + "user@odata.bind": "https://graph.microsoft.com/v1.0/users('%s')" % user_id + } + + ret = session.post(graph_url, json=data) + try: + data = ret.json() + except: + data = ret.text + + if ret.status_code < 300: + return {"success": True, "value": data} + + return {"success": False, "reason": "Bad status code %d - expecting 200." % ret.status_code, "url": graph_url, "details": data} + + # Dosnt work: https://docs.microsoft.com/en-us/graph/api/chat-post-messages?view=graph-rest-beta&tabs=http + def send_message_to_channel(self, tenant_id, client_id, client_secret, team_id, channel_id, user_id, message): + session = self.authenticate(tenant_id, client_id, client_secret) + graph_url = "%s/v1.0/teams/%s/channels/%s/messages" % (self.graph_url, team_id, channel_id) + + #"createdDateTime":"2021-02-04T19:58:15.511Z", + data = { + "from":{ + "user":{ + "id":user_id, + "displayName":"Fredrik Sveum Ødegårdstuen", + "userIdentityType":"aadUser" + } + }, + "body":{ + "contentType":"html", + "content": message, + } + } + + ret = session.post(graph_url, json=data) + try: + data = ret.json() + except: + data = ret.text + + if ret.status_code < 300: + return {"success": True, "value": data} + + return {"success": False, "reason": "Bad status code %d - expecting 200." % ret.status_code, "url": graph_url, "details": data} + + def create_channel_in_team(self, tenant_id, client_id, client_secret, team_id, name, description): + session = self.authenticate(tenant_id, client_id, client_secret) + graph_url = "%s/v1.0/teams/%s/channels" % (self.graph_url, team_id) + + data = { + "displayName": name, + "description": description, + "membershipType": "standard" + } + + ret = session.post(graph_url, json=data) + try: + data = ret.json() + except: + data = ret.text + + if ret.status_code < 300: + return {"success": True, "value": data} + + return {"success": False, "reason": "Bad status code %d - expecting 200." % ret.status_code, "url": graph_url, "details": data} + + def delete_channel(self, tenant_id, client_id, client_secret, team_id, channel_id): + session = self.authenticate(tenant_id, client_id, client_secret) + graph_url = "%s/v1.0/teams/%s/channels/%s" % (self.graph_url, team_id, channel_id) + ret = session.delete(graph_url) + try: + data = ret.json() + except: + data = ret.text + + if ret.status_code < 300: + return {"success": True, "value": data} + + return {"success": False, "reason": "Bad status code %d - expecting 200." % ret.status_code, "url": graph_url, "details": data} + + def list_apps_in_team(self, tenant_id, client_id, client_secret, team_id): + session = self.authenticate(tenant_id, client_id, client_secret) + graph_url = "%s/v1.0/teams/%s/installedApps" % (self.graph_url, team_id) + ret = session.get(graph_url) + try: + data = ret.json() + except: + data = ret.text + + if ret.status_code < 300: + return {"success": True, "value": data} + + return {"success": False, "reason": "Bad status code %d - expecting 200." % ret.status_code, "url": graph_url, "details": data} + + def get_app_in_team(self, tenant_id, client_id, client_secret, team_id, app_id): + session = self.authenticate(tenant_id, client_id, client_secret) + graph_url = "%s/v1.0/teams/%s/installedApps/%s" % (self.graph_url, team_id, app_id) + ret = session.get(graph_url) + try: + data = ret.json() + except: + data = ret.text + + if ret.status_code < 300: + return {"success": True, "value": data} + + return {"success": False, "reason": "Bad status code %d - expecting 200." % ret.status_code, "url": graph_url, "details": data} + + #{ + # "id": "aa39b2f8-3c8d-4ce1-8b8b-7fe02c59ae3e", + # "externalId": null, + # "displayName": "Outgoing Webhook", + # "distributionMethod": "store" + #}, + def add_webhook_to_team(self, tenant_id, client_id, client_secret, team_id): + session = self.authenticate(tenant_id, client_id, client_secret) + #graph_url = "%s/v1.0/teams/%s/installedApps" % (self.graph_url, team_id) + graph_url = "%s/v1.0/chats/%s/installedApps" % (self.graph_url, team_id) + #POST https://graph.microsoft.com/v1.0/chats/19:ea28e88c00e94c7786b065394a61f296@thread.v2/installedApps + + + data = { + "teamsApp@odata.bind": "https://graph.microsoft.com/beta/appCatalogs/teamsApps/aa39b2f8-3c8d-4ce1-8b8b-7fe02c59ae3e" + } + + ret = session.post(graph_url, json=data) + try: + data = ret.json() + except: + data = ret.text + + if ret.status_code < 300: + return {"success": True, "value": data} + + return {"success": False, "reason": "Bad status code %d - expecting 200." % ret.status_code, "url": graph_url, "details": data} + + #POST /teams/87654321-0abc-zqf0-321456789q/installedApps + #Content-type: application/json + + #{ + # "teamsApp@odata.bind":"https://graph.microsoft.com/beta/appCatalogs/teamsApps/12345678-9abc-def0-123456789a" + #} + + +if __name__ == "__main__": + Teams.run() diff --git a/recordedfuture/1.0.0/Dockerfile b/unsupported/microsoft-teams/1.0.0/Dockerfile similarity index 100% rename from recordedfuture/1.0.0/Dockerfile rename to unsupported/microsoft-teams/1.0.0/Dockerfile diff --git a/unsupported/microsoft-teams/1.0.0/MicrosoftTeams-image.png b/unsupported/microsoft-teams/1.0.0/MicrosoftTeams-image.png new file mode 100644 index 0000000000000000000000000000000000000000..d8986bba7f48c3c34a086ecbd36d34271645c13b GIT binary patch literal 104418 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

6aNgbd@Er@>}Y)RgjS?R5Rz{PBnO4XIG$O!+lC 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 literal 0 HcmV?d00001 diff --git a/unsupported/microsoft-teams/1.0.0/README.md b/unsupported/microsoft-teams/1.0.0/README.md new file mode 100644 index 00000000..dba1b839 --- /dev/null +++ b/unsupported/microsoft-teams/1.0.0/README.md @@ -0,0 +1,30 @@ +# Microsoft Teams App + +The MS Teams app for sending an alert to Teams and allowing users to manage alert from Teams. + +![alt text](https://github.com/Shuffle/python-apps/blob/master/microsoft-teams/1.0.0/MicrosoftTeams-image.png?raw=true) + +## Actions + +- Send simple text +- Send rich text +- Send actionable message +- Get user input + +## Requirements + +- Microsoft Teams account. + +## Setup + +1. Go to teams section in Teams app. +2. Select the team then select channel you want to send alert to. (__All the members in same channel will be able to see and react to alert/message__). +3. Go to connectors → incoming webhook select configure. +4. Provide suitable name & picture (optional). +5. Copy webhook url and head over to shuffle. +6. Add Teams app in your workflow, use webhook url in app. + +## Note +- If you are planning on sending actionable message or get user input, you'll need to have webhook running in your workflow (Go to your workflow → Triggers select webhook and start it). +- Once you start webhook you'll see webhook url. Copy & use the same in callback_url for actionable message / user input. +- Read more about webhook [here](https://shuffler.io/docs/triggers#webhook). diff --git a/unsupported/microsoft-teams/1.0.0/api.yaml b/unsupported/microsoft-teams/1.0.0/api.yaml new file mode 100644 index 00000000..89ea36ea --- /dev/null +++ b/unsupported/microsoft-teams/1.0.0/api.yaml @@ -0,0 +1,165 @@ +app_version: 1.0.0 +name: Microsoft Teams +description: Microsoft Teams app for sending an alert to channel. +contact_info: + name: "@ShalinBhavsar" + url: https://github.com/shalin24999 + email: shalinbhavsar17@gmail.com +tags: + - Alert +categories: + - Communication +authentication: + required: true + parameters: + - name: webhook_url + description: Enter webhook of the channels you want to send message to. + example: "https://example.webhook.office.com/123" + required: true + schema: + type: string +actions: + - name: send_simple_text + description: Sends a message to Teams channel. + parameters: + - name: webhook_url + description: Enter webhook of the channels you want to send message to. + required: true + multiline: true + example: 'https://example.webhook.office.com/123' + schema: + type: string + - name: message + description: Message + required: true + multiline: true + example: 'Alert...' + schema: + type: string + returns: + schema: + type: string + - name: send_rich_text + description: Sends a rich text card to channel with link. + parameters: + - name: webhook_url + description: Enter webhook of the channels you want to send message to. + required: true + multiline: true + example: 'https://example.webhook.office.com/123' + schema: + type: string + - name: title + description: Title of the rich text card. + required: false + multiline: false + example: 'Title here' + schema: + type: string + - name: message + description: Message + required: true + multiline: true + example: 'Alert...' + schema: + type: string + - name: link_button_text + description: Text you want to print on redirect button. + required: true + multiline: false + example: 'Shuffle' + schema: + type: string + - name: link_button_url + description: Enter a url you want user to click on. + required: true + multiline: true + example: 'https://yoururlhere.com/' + schema: + type: string + returns: + schema: + type: string + - name: send_actionable_msg + description: Sends message to channel with actions. + parameters: + - name: webhook_url + description: Enter webhook of the channels you want to send message to. + required: true + multiline: true + example: 'https://example.webhook.office.com/123' + schema: + type: string + - name: title + description: Title of the rich text card. + required: false + multiline: false + example: 'Title here' + schema: + type: string + - name: message + description: Message + required: true + multiline: true + example: 'Alert...' + schema: + type: string + - name: choices + description: List of choices to select from + required: false + multiline: true + example: Choice 1,Choice 2,Choice 3 + schema: + type: string + - name: added_information + description: Some extra information to be added to the callback. E.g. an alert + required: true + multiline: true + example: '$new_ticket.ticket_id' + schema: + type: string + - name: callback_url + description: webhook url of your workflow in shuffle + required: true + multiline: false + example: 'https://example.com/123' + schema: + type: string + returns: + schema: + type: string + - name: get_user_input + description: Sends message with text field for user to input to channel. + parameters: + - name: webhook_url + description: Enter webhook of the channels you want to send message to. + required: true + multiline: true + example: 'https://example.webhook.office.com/123' + schema: + type: string + - name: title + description: Title of the rich text card. + required: false + multiline: false + example: 'Title here' + schema: + type: string + - name: message + description: Message + required: true + multiline: true + example: 'Alert...' + schema: + type: string + - name: callback_url + description: webhook url of your workflow in shuffle + required: true + multiline: false + example: 'https://example.com/123' + schema: + type: string + returns: + schema: + type: string +large_image: data:image/png;base64,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 diff --git a/recordedfuture/1.0.0/requirements.txt b/unsupported/microsoft-teams/1.0.0/requirements.txt similarity index 100% rename from recordedfuture/1.0.0/requirements.txt rename to unsupported/microsoft-teams/1.0.0/requirements.txt diff --git a/unsupported/microsoft-teams/1.0.0/src/app.py b/unsupported/microsoft-teams/1.0.0/src/app.py new file mode 100644 index 00000000..0c072f89 --- /dev/null +++ b/unsupported/microsoft-teams/1.0.0/src/app.py @@ -0,0 +1,119 @@ +import socket +import asyncio +import time +import random +import json +import teams #We have made changes to pymsteams module so please use teams.py DO NOT USE pymsteams.py + +from walkoff_app_sdk.app_base import AppBase + +class MsTeams(AppBase): + __version__ = "1.0.0" + app_name = "Microsoft Teams" # this needs to match "name" in api.yaml + + def __init__(self, redis, logger, console_logger=None): + """ + Each app should have this __init__ to set up Redis and logging. + :param redis: + :param logger: + :param console_logger: + """ + super().__init__(redis, logger, console_logger) + + # Write your data inside this function + def send_simple_text(self, webhook_url, message): + try: + myTeamsMessage = teams.connectorcard(str(webhook_url)) # You must create the connectorcard object with the Microsoft Webhook URL + myTeamsMessage.text(message) # Add text to the message. + myTeamsMessage.send()# send the message. + except Exception as e: + return f'{e.__class__} occured' + + return f'Message Sent' + + def send_rich_text(self, webhook_url, title, message, link_button_text, link_button_url): + try: + myTeamsMessage = teams.connectorcard(webhook_url) # You must create the connectorcard object with the Microsoft Webhook URL + myTeamsMessage.title(title) # title for your card + myTeamsMessage.text(message) # Add text to the message. + myTeamsMessage.addLinkButton(str(link_button_text), str(link_button_url)) # for button + myTeamsMessage.send()# send the message. + except Exception as e: + return f'{e.__class__} occured' + + return f'Message Sent' + + def send_actionable_msg(self, webhook_url, title, message, added_information, choices, callback_url): + try: + myTeamsMessage = teams.connectorcard(webhook_url) # You must create the connectorcard object with the Microsoft Webhook URL + myTeamsMessage.title(title) # title for your card + myTeamsMessage.text(message) # Add text to the message. + myTeamsPotentialAction3 = teams.potentialaction(_name = "Select_Action") + + if choices: + for choice in choices.split(","): + choice = choice.strip() + value = { + "choice": choice, + "extra": added_information, + } + + try: + choice_value = json.dumps(value) + except: + print("FAILED ENCODING {}".format(choice)) + choice_value = choice + + myTeamsPotentialAction3.choices.addChoices(choice, choice_value) #option 1 + + else: + value = { + "choice": "ACCEPT", + "extra": added_information, + } + + #print(f"VALUE: {value}") + + try: + accept = json.dumps(value) + except: + print("FAILED ENCODING ACCEPT") + accept = "ACCEPT" + + myTeamsPotentialAction3.choices.addChoices("Accept", accept) #option 1 + + value["choice"] = "REJECT" + try: + deny = json.dumps(value) + except: + print("FAILED ENCODING REJECT") + deny = "REJECT" + + myTeamsPotentialAction3.choices.addChoices("Reject", deny) #option 2 + + myTeamsPotentialAction3.addInput("MultichoiceInput","list","Select Action", False) #Dropdown menu + myTeamsPotentialAction3.addAction("HttpPost","Submit",callback_url) #post request to Shuffle + myTeamsMessage.addPotentialAction(myTeamsPotentialAction3) + myTeamsMessage.send()# send the message. + except Exception as e: + return f'{e} occured' + + return f'Message Sent' + + def get_user_input(self, webhook_url, title, message, callback_url): + try: + myTeamsMessage = teams.connectorcard(webhook_url) # You must create the connectorcard object with the Microsoft Webhook URL + myTeamsMessage.title(title) # Title for your card + myTeamsMessage.text(message) # Add text to the message. + myTeamsPotentialAction1 = teams.potentialaction(_name = "Comment") + myTeamsPotentialAction1.addInput("TextInput","comment", "Your text here..",False) + myTeamsPotentialAction1.addCommentAction("HttpPost","Submit", callback_url) + myTeamsMessage.addPotentialAction(myTeamsPotentialAction1) + myTeamsMessage.send() + except Exception as e: + return f'{e.__class__} occured' + + return f'Message Sent' + +if __name__ == "__main__": + MsTeams.run() diff --git a/unsupported/microsoft-teams/1.0.0/src/teams.py b/unsupported/microsoft-teams/1.0.0/src/teams.py new file mode 100644 index 00000000..31b69079 --- /dev/null +++ b/unsupported/microsoft-teams/1.0.0/src/teams.py @@ -0,0 +1,254 @@ +#!/usr/bin/env python + +# reference: https://github.com/rveachkc/pymsteams/ +# reference: https://dev.outlook.com/connectors/reference + +import requests + +class TeamsWebhookException(Exception): + """custom exception for failed webhook call""" + pass + +class cardsection: + + def title(self, stitle): + # title of the section + self.payload["title"] = stitle + + def activityTitle(self, sactivityTitle): + # Title of the event or action. Often this will be the name of the "actor". + self.payload["activityTitle"] = sactivityTitle + + def activitySubtitle(self, sactivitySubtitle): + # A subtitle describing the event or action. Often this will be a summary of the action. + self.payload["activitySubtitle"] = sactivitySubtitle + + def activityImage(self, sactivityImage): + # URL to image or a data URI with the base64-encoded image inline. + # An image representing the action. Often this is an avatar of the "actor" of the activity. + self.payload["activityImage"] = sactivityImage + + def activityText(self, sactivityText): + # A full description of the action. + self.payload["activityText"] = sactivityText + + def addFact(self, factname, factvalue): + if "facts" not in self.payload.keys(): + self.payload["facts"] = [] + + newfact = { + "name" : factname, + "value" : factvalue + } + self.payload["facts"].append(newfact) + + def addImage(self, simage, ititle=None): + if "images" not in self.payload.keys(): + self.payload["images"] = [] + imobj = {} + imobj["image"] = simage + if ititle: + imobj["title"] = ititle + self.payload["images"].append(imobj) + + + def text(self, stext): + self.payload["text"] = stext + + def linkButton(self, buttontext, buttonurl): + self.payload["potentialAction"] = [ + { + "@context" : "http://schema.org", + "@type" : "ViewAction", + "name" : buttontext, + "target" : [ buttonurl ] + } + ] + + def disableMarkdown(self): + self.payload["markdown"] = False + + def enableMarkdown(self): + self.payload["markdown"] = True + + def dumpSection(self): + return self.payload + + def __init__(self): + self.payload = {} + + + +class potentialaction: + + def addInput(self,_type,_id,title, isMultiline = None): + if "inputs" not in self.payload.keys(): + self.payload["inputs"] = [] + if(self.choices.dumpChoices() == []): + input = { + "@type": _type, + "id": _id, + "isMultiline" :isMultiline, + "title": title + } + else: + input = { + "@type": _type, + "id": _id, + "isMultiline" :str(isMultiline).lower(), + "choices":self.choices.dumpChoices(), + "title": title + } + + self.payload["inputs"].append(input) + + def addAction(self,_type,_name,_target): + if "actions" not in self.payload.keys(): + self.payload["actions"] = [] + action = { + "@type": _type, + "name": _name, + "target": _target, + "body": "{{list.value}}" + } + self.payload["actions"].append(action) + + def addCommentAction(self,_type,_name,_target): + if "actions" not in self.payload.keys(): + self.payload["actions"] = [] + action = { + "@type": _type, + "name": _name, + "target": _target, + "body": "{{comment.value}}" + } + self.payload["actions"].append(action) + + def addOpenURI(self, _name, _targets): + """ + Creates a OpenURI action + + https://docs.microsoft.com/en-us/outlook/actionable-messages/message-card-reference#openuri-action + + :param _name: *Name of the text to appear inside the ActionCard* + :type _name: str + :param _targets: *A list of dictionaries, ex: `{"os": "default", "uri": "https://www..."}`* + :type _targets: list(dict()) + """ + self.payload["@type"] = "OpenUri" + self.payload["name"] = _name + if not isinstance(_targets, list): + raise TypeError("Target must be of type list(dict())") + self.payload["targets"] = _targets + + + def dumpPotentialAction(self): + return self.payload + + def __init__(self, _name, _type = "ActionCard"): + self.payload = {} + self.payload["@type"] = _type + self.payload["name"] = _name + self.choices = choice() + +class choice: + def __init__(self): + self.choices = [] + + def addChoices(self,_display,_value): + self.choices.append({ + "display": _display, + "value": _value + }) + def dumpChoices(self): + return self.choices + +class connectorcard: + + def text(self, mtext): + self.payload["text"] = mtext + + def title(self, mtitle): + self.payload["title"] = mtitle + + def summary(self, msummary): + self.payload["summary"] = msummary + + def color(self, mcolor): + if mcolor.lower() == "red": + self.payload["themeColor"] = "E81123" + else: + self.payload["themeColor"] = mcolor + + def addLinkButton(self, buttontext, buttonurl): + if "potentialAction" not in self.payload: + self.payload["potentialAction"] = [] + + thisbutton = { + "@context" : "http://schema.org", + "@type" : "ViewAction", + "name" : buttontext, + "target" : [ buttonurl ] + } + + self.payload["potentialAction"].append(thisbutton) + + def newhookurl(self, nhookurl): + self.hookurl = nhookurl + + def addSection(self, newsection): + # this function expects a cardsection object + if "sections" not in self.payload.keys(): + self.payload["sections"] = [] + + self.payload["sections"].append(newsection.dumpSection()) + + def addPotentialAction(self, newaction): + # this function expects a potential action object + if "potentialAction" not in self.payload.keys(): + self.payload["potentialAction"] = [] + + self.payload["potentialAction"].append(newaction.dumpPotentialAction()) + + def printme(self): + print("hookurl: %s" % self.hookurl) + print("payload: %s" % self.payload) + + def send(self): + headers = {"Content-Type":"application/json"} + r = requests.post( + self.hookurl, + json=self.payload, + headers=headers, + proxies=self.proxies, + timeout=self.http_timeout, + verify=self.verify, + ) + self.last_http_status = r + + if r.status_code == requests.codes.ok and r.text == '1': # pylint: disable=no-member + return True + else: + raise TeamsWebhookException(r.text) + + def __init__(self, hookurl, http_proxy=None, https_proxy=None, http_timeout=60, verify=None): + self.payload = {} + self.hookurl = hookurl + self.proxies = {} + self.http_timeout = http_timeout + self.verify = verify + self.last_http_response = None + + if http_proxy: + self.proxies['http'] = http_proxy + + if https_proxy: + self.proxies['https'] = https_proxy + + if not self.proxies: + self.proxies = None + + +def formaturl(display, url): + mdurl = "[%s](%s)" % (display, url) + return mdurl diff --git a/twitter/1.0.0/Dockerfile b/unsupported/passivetotal/1.0.0/Dockerfile similarity index 100% rename from twitter/1.0.0/Dockerfile rename to unsupported/passivetotal/1.0.0/Dockerfile diff --git a/passivetotal/1.0.0/api.yaml b/unsupported/passivetotal/1.0.0/api.yaml similarity index 100% rename from passivetotal/1.0.0/api.yaml rename to unsupported/passivetotal/1.0.0/api.yaml diff --git a/unsupported/passivetotal/1.0.0/requirements.txt b/unsupported/passivetotal/1.0.0/requirements.txt new file mode 100644 index 00000000..fd7d3e06 --- /dev/null +++ b/unsupported/passivetotal/1.0.0/requirements.txt @@ -0,0 +1 @@ +requests==2.25.1 \ No newline at end of file diff --git a/passivetotal/1.0.0/src/app.py b/unsupported/passivetotal/1.0.0/src/app.py similarity index 100% rename from passivetotal/1.0.0/src/app.py rename to unsupported/passivetotal/1.0.0/src/app.py diff --git a/vulndb/1.0.0/Dockerfile b/unsupported/recordedfuture/1.0.0/Dockerfile similarity index 100% rename from vulndb/1.0.0/Dockerfile rename to unsupported/recordedfuture/1.0.0/Dockerfile diff --git a/recordedfuture/1.0.0/api.yaml b/unsupported/recordedfuture/1.0.0/api.yaml similarity index 100% rename from recordedfuture/1.0.0/api.yaml rename to unsupported/recordedfuture/1.0.0/api.yaml diff --git a/unsupported/recordedfuture/1.0.0/requirements.txt b/unsupported/recordedfuture/1.0.0/requirements.txt new file mode 100644 index 00000000..fd7d3e06 --- /dev/null +++ b/unsupported/recordedfuture/1.0.0/requirements.txt @@ -0,0 +1 @@ +requests==2.25.1 \ No newline at end of file diff --git a/recordedfuture/1.0.0/src/app.py b/unsupported/recordedfuture/1.0.0/src/app.py similarity index 100% rename from recordedfuture/1.0.0/src/app.py rename to unsupported/recordedfuture/1.0.0/src/app.py diff --git a/unsupported/servicenow/1.0.0/Dockerfile b/unsupported/servicenow/1.0.0/Dockerfile new file mode 100644 index 00000000..364e1531 --- /dev/null +++ b/unsupported/servicenow/1.0.0/Dockerfile @@ -0,0 +1,26 @@ +# Base our app image off of the WALKOFF App SDK image +FROM frikky/shuffle:app_sdk as base + +# We're going to stage away all of the bloat from the build tools so lets create a builder stage +FROM base as builder + +# Install all alpine build tools needed for our pip installs +RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev + +# Install all of our pip packages in a single directory that we can copy to our base image later +RUN mkdir /install +WORKDIR /install +COPY requirements.txt /requirements.txt +RUN pip install --prefix="/install" -r /requirements.txt + +# Switch back to our base image and copy in all of our built packages and source code +FROM base +COPY --from=builder /install /usr/local +COPY src /app + +# Install any binary dependencies needed in our final image +# RUN apk --no-cache add --update my_binary_dependency + +# Finally, lets run our app! +WORKDIR /app +CMD python app.py --log-level DEBUG diff --git a/unsupported/servicenow/1.0.0/api.yaml b/unsupported/servicenow/1.0.0/api.yaml new file mode 100644 index 00000000..4fa62bef --- /dev/null +++ b/unsupported/servicenow/1.0.0/api.yaml @@ -0,0 +1,146 @@ +walkoff_version: 1.0.0 +app_version: 1.0.0 +name: servicenow +description: servicenow app +tags: + - tickets +categories: + - tickets +contact_info: + name: "@frikkylikeme" + url: https://github.com/frikky + email: "frikky@shuffler.io" +authentication: + required: true + parameters: + - name: url + description: The url your instance is at + multiline: false + example: "test.service-now.com" + required: true + schema: + type: string + - name: username + description: The user to authenticate with + multiline: false + example: "username12345" + required: true + schema: + type: string + - name: password + description: The password for the user to authenticate with + multiline: false + example: "pw1234" + required: true + schema: + type: string +actions: + - name: get_ticket + description: Get ticket ids + parameters: + - name: table_name + description: The type to get. Empty as default + multiline: false + example: "incident" + required: true + schema: + type: string + - name: sys_id + description: The ID to get from the table + multiline: false + example: "INC123456" + required: true + schema: + type: string + - name: number + description: The number to get instead of record_id + multiline: false + example: "20" + required: false + schema: + type: string + returns: + schema: + type: string + - name: create_ticket + description: Create a ticket + parameters: + - name: table_name + description: The table to create the ticket in + multiline: false + example: "incident" + required: true + schema: + type: string + - name: body + description: The body of the ticket + multiline: true + example: "{'short_description':'Unable to connect to office wifi','assignment_group':'287ebd7da9fe198100f92cc8d1d2154e','urgency':'2','impact':'2'}" + required: true + schema: + type: string + - name: file_id + description: Optional file to attach + multiline: false + example: "ca0c88a6-626e-4235-896f-ca18c96fd48e" + required: false + schema: + type: string + returns: + schema: + type: string + - name: update_ticket + description: Update a ticket + parameters: + - name: table_name + description: The table to create the ticket in + multiline: false + example: "incident" + required: true + schema: + type: string + - name: sys_id + description: The ticket to edit + multiline: false + example: "incident" + required: true + schema: + type: string + - name: body + description: JSON data of the data to replace + multiline: true + example: "{'short_description':'Unable to connect to office wifi','assignment_group':'287ebd7da9fe198100f92cc8d1d2154e','urgency':'2','impact':'2'}" + required: true + schema: + type: string + - name: file_id + description: Optional file to attach + multiline: false + example: "ca0c88a6-626e-4235-896f-ca18c96fd48e" + required: false + schema: + type: string + returns: + schema: + type: string + - name: list_table + description: Get ticket ids + parameters: + - name: table_name + description: The type to get. Empty as default + multiline: false + example: "incident" + required: true + schema: + type: string + - name: limit + description: The limit of items to get + multiline: false + example: "1" + required: false + schema: + type: string + returns: + schema: + type: string +large_image: data:image/jpg;base64,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 diff --git a/unsupported/servicenow/1.0.0/requirements.txt b/unsupported/servicenow/1.0.0/requirements.txt new file mode 100644 index 00000000..fd7d3e06 --- /dev/null +++ b/unsupported/servicenow/1.0.0/requirements.txt @@ -0,0 +1 @@ +requests==2.25.1 \ No newline at end of file diff --git a/unsupported/servicenow/1.0.0/src/app.py b/unsupported/servicenow/1.0.0/src/app.py new file mode 100755 index 00000000..d85dc832 --- /dev/null +++ b/unsupported/servicenow/1.0.0/src/app.py @@ -0,0 +1,204 @@ +import time +import json +import random +import socket +import asyncio +import requests + +from walkoff_app_sdk.app_base import AppBase + +class Servicenow(AppBase): + __version__ = "1.0.0" + app_name = "servicenow" + + def __init__(self, redis, logger, console_logger=None): + """ + Each app should have this __init__ to set up Redis and logging. + :param redis: + :param logger: + :param console_logger: + """ + super().__init__(redis, logger, console_logger) + + def send_request(self, url, username, password, path, method='get', body=None, params=None, headers=None, json=None, files=None): + body = body if body is not None else {} + params = params if params is not None else {} + + url = '{}{}'.format(url, path) + print("HEADERS: %s" % headers) + if not headers and files == None: + headers = { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + } + + if files: + # Not supported in v2 + url = url.replace('v2', 'v1') + #{'file': ('report.csv', 'some,data,to,send\nanother,row,to,send\n')} + #file_entry = file['id'] + #file_name = file['name'] + try: + #shutil.copy(demisto.getFilePath(file_entry)['path'], file_name) + #with open(file_name, 'rb') as f: + #files = {'file': f} + + try: + res = requests.request(method, url, headers=headers, params=params, data=body, files=files, json=json, auth=(username, password)) + except requests.exceptions.ReadTimeout as e: + return "Readtimeout: %s" % e + except requests.exceptions.ConnectionError as e: + return "ConnectionError: %s" % e + + #shutil.rmtree(demisto.getFilePath(file_entry)['name'], ignore_errors=True) + except Exception as e: + return 'Failed to upload file - ' + str(e) + else: + try: + res = requests.request(method, url, headers=headers, data=json.dumps(body) if body else {}, json=json, params=params, auth=(username, password)) + except requests.exceptions.ReadTimeout as e: + return "Readtimeout: %s" % e + except requests.exceptions.ConnectionError as e: + return "ConnectionError: %s" % e + + try: + obj = res.json() + except Exception as e: + if not res.content: + return '' + return 'Error parsing reply - {} - {}'.format(res.content, str(e)) + + if 'error' in obj: + message = obj.get('error', {}).get('message') + details = obj.get('error', {}).get('detail') + if message == 'No Record found': + return { + # Return an empty results array + 'result': [] + } + return 'ServiceNow Error: {}, details: {}'.format(message, details) + + if res.status_code < 200 or res.status_code >= 300: + return 'Got status code {} with url {} with body {} with headers {}'.format(str(res.status_code), url, str(res.content), str(res.headers)) + + #print("RES: %s" % res) + #print("TEXT: %s" % res.text) + return res.text + + def get_ticket(self, url, username, password, table_name, sys_id, number=None): + path = None + query_params = {} # type: Dict + if sys_id: + path = "/api/now/v1/table/%s/%s" % (table_name, sys_id) + elif number: + path = '/api/now/v1/table/%s' % table_name + query_params = { + 'number': number + } + else: + # Only in cases where the table is of type ticket + return 'servicenow-get-ticket requires either ticket ID or ticket number' + + print("PATH: %s" % path) + return self.send_request(url, username, password, path, 'get', params=query_params) + + def list_table(self, url, username, password, table_name, limit=1): + query_params = { + "sysparm_limit": limit, + } + + #path = '/table/%s' % table_name + path = "/api/now/v1/table/%s" % table_name + + return self.send_request(url, username, password, path, 'get', params=query_params) + + def create_ticket(self, url, username, password, table_name, body, file_id=""): + if not isinstance(body, list) and not isinstance(body, object) and not isinstance(body, dict): + try: + data = json.loads(body) + except json.decoder.JSONDecodeError as e: + return {"success": False, "reason": e} + else: + data = body + + + path = "/api/now/v1/table/%s" % table_name + query_params = {} + base_request = self.send_request(url, username, password, path, 'post', params=query_params, json=data) + + if file_id: + tmp_file = self.get_file(file_id) + files = {'file': (tmp_file["filename"], tmp_file["data"])} + + try: + parsed_return = json.loads(base_request) + except: + print("[INFO] Failed parsed_return loading") + return base_request + + ticket_id = parsed_return["result"]["sys_id"] + params = { + "file_name": tmp_file["filename"], + "table_name": table_name, + "table_sys_id": ticket_id, + } + + filepath = "/api/now/v1/attachment/file" + file_request = self.send_request(url, username, password, filepath, 'post', params=params, files=files, headers={}) + print(file_request) + + return base_request + + def update_ticket(self, url, username, password, table_name, sys_id, body, file_id=""): + if not isinstance(body, list) and not isinstance(body, object) and not isinstance(body, dict): + try: + data = json.loads(body) + except json.decoder.JSONDecodeError as e: + return {"success": False, "reason": e} + else: + data = body + + + path = "/api/now/v1/table/%s/%s" % (table_name, sys_id) + query_params = {} + base_request = self.send_request(url, username, password, path, 'patch', params=query_params, json=data) + + if file_id: + tmp_file = self.get_file(file_id) + files = {'file': (tmp_file["filename"], tmp_file["data"])} + + try: + parsed_return = json.loads(base_request) + except: + print("[INFO] Failed parsed_return loading") + return base_request + + ticket_id = parsed_return["result"]["sys_id"] + params = { + "file_name": tmp_file["filename"], + "table_name": table_name, + "table_sys_id": ticket_id, + } + + filepath = "/api/now/v1/attachment/file" + file_request = self.send_request(url, username, password, filepath, '', params=params, files=files, headers={}) + print(file_request) + + return base_request + +# Run the actual thing after we've checked params +def run(request): + action = request.get_json() + print(action) + print(type(action)) + authorization_key = action.get("authorization") + current_execution_id = action.get("execution_id") + + if action and "name" in action and "app_name" in action: + Servicenow.run(action) + return f'Attempting to execute function {action["name"]} in app {action["app_name"]}' + else: + return f'Invalid action' + +if __name__ == "__main__": + Servicenow.run() diff --git a/thehive/1.0.0/Dockerfile b/unsupported/splunk/1.0.0/Dockerfile similarity index 100% rename from thehive/1.0.0/Dockerfile rename to unsupported/splunk/1.0.0/Dockerfile diff --git a/unsupported/splunk/1.0.0/api.yaml b/unsupported/splunk/1.0.0/api.yaml new file mode 100644 index 00000000..f63c9dac --- /dev/null +++ b/unsupported/splunk/1.0.0/api.yaml @@ -0,0 +1,62 @@ +walkoff_version: 1.0.0 +app_version: 1.0.0 +name: splunk +description: Splunk integration with WALKOFF +tags: + - SIEM + - search +categories: + - SIEM +contact_info: + name: "@frikkylikeme" + url: https://github.com/frikky +authentication: + required: true + parameters: + - name: url + description: The Splunk URL + required: true + example: "http://splunk:8081" + schema: + type: string + - name: username + description: The Splunk username + example: username@splunk.com + required: true + schema: + type: string + - name: password + description: The Splunk password + required: true + example: "******" + schema: + type: string + +actions: + - name: SplunkQuery + description: Returns the amount of search results + parameters: + - name: query + description: The Splunk query to run + required: true + schema: + type: string + - name: result_limit + description: Splunk amount limit + required: false + schema: + type: string + - name: earliest_time + description: The timeframe to use (e.g. -48h) + required: false + schema: + type: string + - name: latest_time + description: The timeframe to use (e.g. -48h) + required: false + schema: + type: string + returns: + schema: + type: string +large_image: data:image/jpg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAcFBgYGBQcGBgYICAcJCxIMCwoKCxcQEQ0SGxccHBoXGhkdISokHR8oIBkaJTIlKCwtLzAvHSM0ODQuNyouLy7/2wBDAQgICAsKCxYMDBYuHhoeLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi7/wAARCABkAGQDAREAAhEBAxEB/8QAGQABAAMBAQAAAAAAAAAAAAAAAAIEBQYD/8QALRAAAQQCAQMBCAEFAAAAAAAAAAECAwQFERIGITFBExQiMlFhcYGxFRYjQkP/xAAaAQEAAwEBAQAAAAAAAAAAAAAAAQIDBAUG/8QAIxEBAAICAgICAgMAAAAAAAAAAAECAxESIQQxQVETIhQj8P/aAAwDAQACEQMRAD8A0T5l5IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABJsb3Nc5rHOa35lRqqifn6E6k0iQAACT45I9c43N2m05NVNoTMTHs0iQAAAAAkxrnvaxqbc5UaifVVJiNzobWUwUeNclebJwuu/ByrpG7tyXXZ3hTfLgjH1Nu/prfFx632tWOl2VL7aNzM1IZpNeyRWOVXb+v0Tfbv5L28Xjbja0bWnDqdTK/i6NjGYjqqnaaiSxwt8L2VOLtKhrjpOPHkrZelZrW8S5/M4eXFy1I5J2SLZjSRqtRU4oq613OXLhnHMRM+2F8c1137aX9o2lyVnHtuQ84IGzK5WqiKiqqa+3jybfw7c5pv1G2n4J5TXarP0+n9JsZGnk61xtdP8zI0VOH4VfJSfH/Sb1tE6VnF+s2id6b3UuNsZS7ha0GkRKSOkkd8sbe21U6PIxTktSI+muSs2msR9OXoY5bdi3UrzVppWJqJXScEkXf8Apvyv2U5KYuVprGtsK03MxClarWKkyw2YXwyp5a9ulM7Vms6tGlZiYnUvEqgAAThbzlYzm2Pk5E5uXSN7+VUmI3JDuLz2yYVrc3YpzZCKZjak0EqPfI3kndden5/k9G87x/2zEzE9Oq3df39/CfVGK996oSdt2rHGxI/bJLKjHRInfel87T6E+Ri5Zt7j42nLTlfe07mYpX4uqHxTxo11dkUPJyIsukd3RF891JvmpeMmp+EzkrbkrZ2GtmIsTkIMjTjghhayb2kunM0qKvw+VX7FM1YyxW0WjUQreIvxmJati9SXPZiRLcHB+NaxrvaJpzvi7J38m1slPyXnfw0m0crd/Dl+nZ4I+muoYpZo2SSQIjGuciK5dL4T1OLBMRhyRtz45iKWdHdy+Pe+lirM8bqVyk2KWSN6bif21tU8J+TrvmpMxjmephtN69Vn1MOLZj6TMhZgt5SJkEH/AFiTmsvfsjUT1/g4Ix1i0xa3Uf7pzRWNzEysZnNR3KUOOrQye7Qu5Nlsv5yu/fon2L5c8WrFKx1H37Wvk3HGGGczIAAAJwuSOZkmvlcjl166XZMdTtMe2l1Jk48xlZLzIXRNcxreLlRVTSGvkZYy35RC+W/O22UYsz9IA/QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/2Q== diff --git a/unsupported/splunk/1.0.0/docker-compose.yml b/unsupported/splunk/1.0.0/docker-compose.yml new file mode 100644 index 00000000..ad612c5d --- /dev/null +++ b/unsupported/splunk/1.0.0/docker-compose.yml @@ -0,0 +1,14 @@ +version: '3.4' +services: + splunk: + build: + context: . + dockerfile: Dockerfile + env_file: + - env.txt + restart: "no" + deploy: + mode: replicated + replicas: 10 + restart_policy: + condition: none diff --git a/unsupported/splunk/1.0.0/env.txt b/unsupported/splunk/1.0.0/env.txt new file mode 100644 index 00000000..b5568707 --- /dev/null +++ b/unsupported/splunk/1.0.0/env.txt @@ -0,0 +1,4 @@ +REDIS_URI=redis://redis +REDIS_ACTION_RESULT_CH=action-results +REDIS_ACTION_RESULTS_GROUP=action-results-group +APP_NAME=splunk diff --git a/unsupported/splunk/1.0.0/requirements.txt b/unsupported/splunk/1.0.0/requirements.txt new file mode 100644 index 00000000..c5a5f6ea --- /dev/null +++ b/unsupported/splunk/1.0.0/requirements.txt @@ -0,0 +1,2 @@ +python-magic==0.4.18 +requests==2.25.1 \ No newline at end of file diff --git a/unsupported/splunk/1.0.0/src/app.py b/unsupported/splunk/1.0.0/src/app.py new file mode 100644 index 00000000..a9a10be4 --- /dev/null +++ b/unsupported/splunk/1.0.0/src/app.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import asyncio +import time +import random +import requests +import urllib3 +import json + +from walkoff_app_sdk.app_base import AppBase + +class Splunk(AppBase): + """ + Splunk integration for WALKOFF with some basic features + """ + __version__ = "1.0.0" + app_name = "splunk" + + def __init__(self, redis, logger, console_logger=None): + """ + Each app should have this __init__ to set up Redis and logging. + :param redis: + :param logger: + :param console_logger: + """ + self.verify = False + self.timeout = 10 + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + super().__init__(redis, logger, console_logger) + + def echo(self, input_data): + return input_data + + def run_search(self, auth, url, query): + url = '%s/services/search/jobs?output_mode=json' % (url) + ret = requests.post(url, auth=auth, data=query, timeout=self.timeout, verify=False) + return ret + + def get_search(self, auth, url, search_sid): + # Wait for search to be done? + firsturl = '%s/services/search/jobs/%s?output_mode=json' % (url, search_sid) + print("STARTED FUNCTION WITH URL %s" % firsturl) + time.sleep(0.2) + maxrunduration = 30 + ret = "No results yet" + while(True): + try: + ret = requests.get(firsturl, auth=auth, timeout=self.timeout, verify=False) + except requests.exceptions.ConnectionError: + print("Sleeping for 1 second") + time.sleep(1) + continue + + try: + content = ret.json()["entry"][0]["content"] + except KeyError as e: + print("\nKEYERROR: %s\n" % content) + time.sleep(1) + continue + + try: + if content["resultCount"] > 0 or content["isDone"] or content["isFinalized"] or content["runDuration"] > maxrunduration: + print("CONTENT PRE EVENTS: ", content) + eventsurl = '%s/services/search/jobs/%s/events' % (url, search_sid) + print("Running events check towards %s" % eventsurl) + try: + newret = requests.get(eventsurl, auth=auth, timeout=self.timeout, verify=False) + if ret.status_code < 300 and ret.status_code >= 200: + return newret.text + else: + return "Bad status code for events: %sd", ret.status_code + except requests.exceptions.ConnectionError: + return "Events requesterror: %s" % e + except KeyError: + try: + return ret.json()["messages"] + except KeyError as e: + return "KeyError: %s" % e + + time.sleep(1) + + return ret + + def SplunkQuery(self, url, username, password, query, result_limit=100, earliest_time="-24h", latest_time="now"): + auth = (username, password) + + # "latest_time": "now" + query = { + "search": "| search %s" % query, + "exec_mode": "normal", + "count": result_limit, + "earliest_time": earliest_time, + "latest_time": latest_time + } + + print("Current search: %s" % query["search"]) + + try: + ret = self.run_search(auth, url, query) + except requests.exceptions.ConnectTimeout as e: + print("Timeout: %s" % e) + return "Timeout: %s" % e + + if ret.status_code != 201: + print("Bad status code: %d" % ret.status_code) + return "Bad status code: %d" % ret.status_code + + search_id = ret.json()["sid"] + + print("Search ID: %s" % search_id) + + ret = self.get_search(auth, url, search_id) + return ret + #if len(ret.json()["entry"]) == 1: + # count = ret.json()["entry"][0]["content"]["resultCount"] + # print("Result: %d" % count) + # return str(count) + + #print("No results (or wrong?): %d" % (len(ret.json()["entry"]))) + #return "No results" + +if __name__ == "__main__": + Splunk.run() diff --git a/unsupported/testing/1.0.0/Dockerfile b/unsupported/testing/1.0.0/Dockerfile new file mode 100644 index 00000000..364e1531 --- /dev/null +++ b/unsupported/testing/1.0.0/Dockerfile @@ -0,0 +1,26 @@ +# Base our app image off of the WALKOFF App SDK image +FROM frikky/shuffle:app_sdk as base + +# We're going to stage away all of the bloat from the build tools so lets create a builder stage +FROM base as builder + +# Install all alpine build tools needed for our pip installs +RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev + +# Install all of our pip packages in a single directory that we can copy to our base image later +RUN mkdir /install +WORKDIR /install +COPY requirements.txt /requirements.txt +RUN pip install --prefix="/install" -r /requirements.txt + +# Switch back to our base image and copy in all of our built packages and source code +FROM base +COPY --from=builder /install /usr/local +COPY src /app + +# Install any binary dependencies needed in our final image +# RUN apk --no-cache add --update my_binary_dependency + +# Finally, lets run our app! +WORKDIR /app +CMD python app.py --log-level DEBUG diff --git a/unsupported/testing/1.0.0/api.yaml b/unsupported/testing/1.0.0/api.yaml new file mode 100644 index 00000000..e1ee1c6c --- /dev/null +++ b/unsupported/testing/1.0.0/api.yaml @@ -0,0 +1,178 @@ +app_version: 1.0.0 +name: Testing +description: Debugging app for Shuffle +tags: + - Testing +categories: + - Testing +contact_info: + name: "@frikkylikeme" + url: https://shuffler.io + email: frikky@shuffler.io +actions: + - name: hello_world + description: Returns Hello World from the hostname the action is run on + returns: + example: HELLO WORLD FROM host.name + returns: + schema: + type: string + - name: repeat_back_to_me + description: Repeats the call parameter + parameters: + - name: call + description: The message to repeat + required: true + multiline: true + example: "REPEATING: Hello world" + schema: + type: string + returns: + schema: + type: string + - name: repeat_back_to_me_multi + description: Repeats the call parameter + parameters: + - name: call + description: The message to repeat + required: true + multiline: true + example: "REPEATING: Hello world" + schema: + type: string + - name: call2 + description: The message to repeat + required: true + multiline: true + example: "REPEATING: Hello world" + schema: + type: string + - name: call3 + description: The message to repeat + required: true + multiline: true + example: "REPEATING: Hello world" + schema: + type: string + returns: + schema: + type: string + - name: return_plus_one + description: Increments the number parameter by 1 + parameters: + - name: number + description: number to increment + required: true + schema: + type: number + example: number(2) + returns: + schema: + type: number + - name: get_type + description: Get the type of a variable + parameters: + - name: value + description: The value to check + required: true + example: '{"return": number(0)}' + schema: + type: string + returns: + schema: + type: number + - name: pause + description: Pause execution by the seconds parameter + parameters: + - name: seconds + description: seconds to pause for + required: true + example: number(3) + schema: + type: number + returns: + schema: + type: number + - name: raise_error + description: This function doesn't exist and is here to test errors + returns: + schema: + type: string + - name: input_options_test + description: Input testing Shuffle + parameters: + - name: call + description: The message to repeat + options: + - hey + - how + - are + - you + required: true + multiline: true + example: "REPEATING: Hello world" + schema: + type: string + returns: + schema: + type: string + - name: get_file_value + description: This function is made for reading file(s), printing their data + parameters: + - name: filedata + description: The files + required: true + multiline: true + example: "REPEATING: Hello world" + schema: + type: file + returns: + schema: + type: string + - name: create_file + description: Returns uploaded file data + parameters: + - name: filename + description: + required: true + multiline: false + example: "test.txt" + schema: + type: string + - name: data + description: + required: true + multiline: true + example: "Some data to put in the file" + schema: + type: string + returns: + schema: + type: file + - name: download_file + description: Downloads a file from a URL + parameters: + - name: url + description: + required: true + multiline: false + example: "https://secure.eicar.org/eicar.com.txt" + schema: + type: string + returns: + schema: + type: string + - name: delete_file + description: Deletes a file based on ID + parameters: + - name: file_id + description: + required: true + multiline: false + example: "Some data to put in the file" + schema: + type: string + returns: + schema: + type: string +large_image: data:image/png;base64,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 diff --git a/unsupported/testing/1.0.0/requirements.txt b/unsupported/testing/1.0.0/requirements.txt new file mode 100644 index 00000000..fd7d3e06 --- /dev/null +++ b/unsupported/testing/1.0.0/requirements.txt @@ -0,0 +1 @@ +requests==2.25.1 \ No newline at end of file diff --git a/unsupported/testing/1.0.0/run b/unsupported/testing/1.0.0/run new file mode 100755 index 00000000..e73f748d --- /dev/null +++ b/unsupported/testing/1.0.0/run @@ -0,0 +1,17 @@ +#!/bin/sh +docker stop frikky/shuffle:testing_1.0.0 --force +docker rm frikky/shuffle:testing_1.0.0 --force +docker rmi frikky/shuffle:testing_1.0.0 --force + +docker build . -t frikky/shuffle:testing_1.0.0 + +echo "RUNNING!\n\n" +docker run \ + --env CALLBACK_URL="http://192.168.239.144:5001" \ + --env ACTION='{"app_name":"testing","app_version":"1.0.0","errors":[],"id_":"13fa4c3f-8991-3ade-b90d-f326fd4941dd","is_valid":true,"label":"random_number","environment":"onprem","name":"random_number","parameters":[],"position":{"x":178.07868996109607,"y":457.28345902971614},"priority":3}' \ + --env FUNCTION_APIKEY="asdasd" \ + --env EXECUTIONID="2349bf96-51ad-68d2-5ca6-75ef8f7ee814" \ + --env AUTHORIZATION="8e344a2e-db51-448f-804c-eb959a32c139" \ + frikky/shuffle:testing_1.0.0 + +docker push frikky/shuffle:testing_1.0.0 diff --git a/unsupported/testing/1.0.0/src/app.py b/unsupported/testing/1.0.0/src/app.py new file mode 100644 index 00000000..de090ef2 --- /dev/null +++ b/unsupported/testing/1.0.0/src/app.py @@ -0,0 +1,101 @@ +import socket +import asyncio +import time +import random +import json +import requests + +from walkoff_app_sdk.app_base import AppBase + +class HelloWorld(AppBase): + """ + An example of a Walkoff App. + Inherit from the AppBase class to have Redis, logging, and console logging set up behind the scenes. + """ + __version__ = "1.0.0" + app_name = "hello_world" # this needs to match "name" in api.yaml + + def __init__(self, redis, logger, console_logger=None): + """ + Each app should have this __init__ to set up Redis and logging. + :param redis: + :param logger: + :param console_logger: + """ + super().__init__(redis, logger, console_logger) + + def hello_world(self): + """ + Returns Hello World from the hostname the action is run on + :return: Hello World from your hostname + """ + message = f"Hello World from {socket.gethostname()} in workflow {self.current_execution_id}!" + + # This logs to the docker logs + self.logger.info(message) + + return message + + def repeat_back_to_me(self, call): + return call + + def repeat_back_to_me_multi(self, call, call2, call3): + return {"call1": call, "call2": call2, "call3": call3} + + def return_plus_one(self, number): + return int(number) + 1 + + def pause(self, seconds): + time.sleep(seconds) + return "Waited %d seconds" % seconds + + def get_type(self, value): + return "Type: %s" % type(value) + + def input_options_test(self, call): + return "Value: %s" % call + + def get_file_value(self, filedata): + if filedata == None: + return "File is empty?" + + print("INSIDE APP DATA: %s" % filedata) + return "%s" % filedata["data"].decode() + + def create_file(self, filename, data): + print("Inside function") + filedata = { + "filename": filename, + "data": data, + } + + fileret = self.set_files([filedata]) + value = {"success": True, "file_ids": fileret} + return value + #print("Done with upload function") + + #return ("Successfully put your data in a file", filedata) + + def download_file(self, url): + ret = requests.get(url, verify=False) + fileret = self.set_files([{ + "filename": "downloaded", + "data": ret.content, + }]) + + value = {"success": True, "file_ids": fileret} + return value + + #return ("Successfully put your data in a file", filedata) + + def delete_file(self, file_id): + headers = { + "Authorization": "Bearer %s" % self.authorization, + } + print("HEADERS: %s" % headers) + + ret = requests.delete("%s/api/v1/files/%s?execution_id=%s" % (self.base_url, file_id, self.current_execution_id), headers=headers) + return ret.text + +if __name__ == "__main__": + HelloWorld.run() diff --git a/unsupported/testing/1.0.0/tmp.py b/unsupported/testing/1.0.0/tmp.py new file mode 100644 index 00000000..2c3698ea --- /dev/null +++ b/unsupported/testing/1.0.0/tmp.py @@ -0,0 +1,128 @@ +import json +import re + +# This whole thing should be recursive. +basejson = [{'highlight_ranges': {}, 'message': {'Alert': 'Account Manipulation', '_id': 'd097c6f2-f6b6-11ea-aaa1-0050569f425d', 'timestamp': '2020-09-14T18:19:24.427Z'}, 'index': 'test', 'decoration_stats': None}, {'highlight_ranges': {}, 'message': {'Alert': 'Account Manipulation', '_id': 'd099c2c3-f6b6-11ea-aaa1-0050569f425d', 'timestamp': '2020-09-14T18:19:24.427Z'}, 'index': 'test', 'decoration_stats': None}, {'highlight_ranges': {}, 'message': {'Alert': 'Account Manipulation', '_id': 'd097c6f2-f6b6-11ea-aaa1-0050569f425d', 'timestamp': '2020-09-14T18:19:24.427Z'}, 'index': 'mitre_0', 'decoration_stats': None}, {'highlight_ranges': {}, 'message': {'Alert': 'Account Manipulation', '_id': 'd099c2c3-f6b6-11ea-aaa1-0050569f425d', 'timestamp': '2020-09-14T18:19:24.427Z'}, 'index': 'mitre_0', 'decoration_stats': None}, {'highlight_ranges': {}, 'message': {'Alert': 'Notepad connecting to the internet', '_id': 'c789d084-f6b6-11ea-aaa1-0050569f425d', 'timestamp': '2020-09-14T18:19:09.444Z'}, 'index': '1_207', 'decoration_stats': None}, {'highlight_ranges': {}, 'message': {'Alert': 'Notepad connecting to the internet', '_id': 'c789d084-f6b6-11ea-aaa1-0050569f425d', 'timestamp': '2020-09-14T18:19:09.444Z'}, 'index': 'mitre_0', 'decoration_stats': None}, {'highlight_ranges': {}, 'message': {'Alert': 'Obfuscating Hacking Commands', '_id': 'ae8ad8f5-f6b5-11ea-aaa1-0050569f425d', 'timestamp': '2020-09-14T18:11:17.202Z'}, 'index': 'mitre_0', 'decoration_stats': None}, {'highlight_ranges': {}, 'message': {'Alert': 'Account Manipulation', '_id': '0f9d3001-f6b3-11ea-aaa1-0050569f425d', 'timestamp': '2020-09-14T17:52:31.810Z'}, 'index': 'test_201', 'decoration_stats': None}, {'highlight_ranges': {}, 'message': {'Alert': 'Account Manipulation', '_id': '0f9d3000-f6b3-11ea-aaa1-0050569f425d', 'timestamp': '2020-09-14T17:52:31.810Z'}, 'index': 'test_201', 'decoration_stats': None}, {'highlight_ranges': {}, 'message': {'Alert': 'Account Manipulation', '_id': '0f9d3001-f6b3-11ea-aaa1-0050569f425d', 'timestamp': '2020-09-14T17:52:31.810Z'}, 'index': 'mitre_0', 'decoration_stats': None}, {'highlight_ranges': {}, 'message': {'Alert': 'Account Manipulation', '_id': '0f9d3000-f6b3-11ea-aaa1-0050569f425d', 'timestamp': '2020-09-14T17:52:31.810Z'}, 'index': 'mitre_0', 'decoration_stats': None}] +#basejson = json.loads(baseresult) + +#ACTUAL: [('$Start_node.#.message', 'Start_node.', 'message')] +input_data = "$Start_node.#4:max.message.Alert" + + +def recurse_loop(basejson, parsersplit): + #parsersplit = input_data.split(".") + + match = "#(\d+):?-?([0-9a-z]+)?#?" + print("Split: %s\n%s" % (parsersplit, basejson)) + try: + outercnt = 0 + for value in parsersplit: + print("VALUE: %s\n" % value) + actualitem = re.findall(match, value, re.MULTILINE) + if value == "#": + newvalue = [] + for innervalue in basejson: + # 1. Check the next item (message) + # 2. Call this function again + + try: + ret = recurse_loop(innervalue, parsersplit[outercnt+1:]) + except IndexError: + print("INDEXERROR: ", parsersplit[outercnt]) + #ret = innervalue + ret = recurse_loop(innervalue, parsersplit[outercnt:]) + + print(ret) + #exit() + newvalue.append(ret) + + return newvalue + elif len(actualitem) > 0: + # FIXME: This is absolutely not perfect. + print("IN HERE: ", actualitem) + + newvalue = [] + firstitem = actualitem[0][0] + seconditem = actualitem[0][1] + if seconditem == "": + print("In first") + basejson = basejson[int(firstitem)] + else: + if seconditem == "max": + seconditem = len(basejson) + if seconditem == "min": + seconditem = 0 + + newvalue = [] + for i in range(int(firstitem), int(seconditem)): + # 1. Check the next item (message) + # 2. Call this function again + print("Base: %s" % basejson[i]) + + try: + ret = recurse_loop(basejson[i], parsersplit[outercnt+1:]) + except IndexError: + print("INDEXERROR: ", parsersplit[outercnt]) + #ret = innervalue + ret = recurse_loop(innervalue, parsersplit[outercnt:]) + + print(ret) + #exit() + newvalue.append(ret) + + return newvalue + else: + #print("BEFORE NORMAL VALUE: ", basejson, value) + if len(value) == 0: + return basejson + + if isinstance(basejson[value], str): + print(f"LOADING STRING '%s' AS JSON" % basejson[value]) + try: + basejson = json.loads(basejson[value]) + except json.decoder.JSONDecodeError as e: + print("RETURNING BECAUSE '%s' IS A NORMAL STRING" % basejson[value]) + return basejson[value] + else: + basejson = basejson[value] + + outercnt += 1 + + except KeyError as e: + print("Lower keyerror: %s" % e) + #return basejson + #return "KeyError: Couldn't find key: %s" % e + + return basejson + +ret = recurse_loop(basejson, input_data.split(".")[1:]) +print(ret) + + + + # FIXME - not recursive - should go deeper if there are more # + #print("HANDLE RECURSIVE LOOP OF %s" % basejson) + #returnlist = [] + #try: + # for innervalue in basejson: + # print("Value: %s" % innervalue[parsersplit[cnt+1]]) + # returnlist.append(innervalue[parsersplit[cnt+1]]) + #except IndexError as e: + # print("Indexerror inner: %s" % e) + # # Basically means its a normal list, not a crazy one :) + # # Custom format for ${name[0,1,2,...]}$ + # indexvalue = "${NO_SPLITTER%s}$" % json.dumps(basejson) + # if len(returnlist) > 0: + # indexvalue = "${NO_SPLITTER%s}$" % json.dumps(returnlist) + + # print("INDEXVAL: ", indexvalue) + # return indexvalue + #except TypeError as e: + # print("TypeError inner: %s" % e) + + ## Example format: ${[]}$ + #parseditem = "${%s%s}$" % (parsersplit[cnt+1], json.dumps(returnlist)) + #print("PARSED LOOP ITEM: %s" % parseditem) + + ## FIXME: Always only does one iter here :( + #return parseditem diff --git a/thehive/1.1.0/Dockerfile b/unsupported/thehive/1.0.0/Dockerfile similarity index 100% rename from thehive/1.1.0/Dockerfile rename to unsupported/thehive/1.0.0/Dockerfile diff --git a/thehive/1.0.0/api.yaml b/unsupported/thehive/1.0.0/api.yaml similarity index 100% rename from thehive/1.0.0/api.yaml rename to unsupported/thehive/1.0.0/api.yaml diff --git a/thehive/1.0.0/docker-compose.yml b/unsupported/thehive/1.0.0/docker-compose.yml similarity index 100% rename from thehive/1.0.0/docker-compose.yml rename to unsupported/thehive/1.0.0/docker-compose.yml diff --git a/thehive/1.0.0/env.txt b/unsupported/thehive/1.0.0/env.txt similarity index 100% rename from thehive/1.0.0/env.txt rename to unsupported/thehive/1.0.0/env.txt diff --git a/thehive/1.0.0/requirements.txt b/unsupported/thehive/1.0.0/requirements.txt similarity index 100% rename from thehive/1.0.0/requirements.txt rename to unsupported/thehive/1.0.0/requirements.txt diff --git a/thehive/1.0.0/run b/unsupported/thehive/1.0.0/run similarity index 100% rename from thehive/1.0.0/run rename to unsupported/thehive/1.0.0/run diff --git a/thehive/1.0.0/src/app.py b/unsupported/thehive/1.0.0/src/app.py similarity index 100% rename from thehive/1.0.0/src/app.py rename to unsupported/thehive/1.0.0/src/app.py diff --git a/thehive/1.1.1/Dockerfile b/unsupported/thehive/1.1.0/Dockerfile similarity index 100% rename from thehive/1.1.1/Dockerfile rename to unsupported/thehive/1.1.0/Dockerfile diff --git a/thehive/1.1.0/api.yaml b/unsupported/thehive/1.1.0/api.yaml similarity index 100% rename from thehive/1.1.0/api.yaml rename to unsupported/thehive/1.1.0/api.yaml diff --git a/thehive/1.1.0/docker-compose.yml b/unsupported/thehive/1.1.0/docker-compose.yml similarity index 100% rename from thehive/1.1.0/docker-compose.yml rename to unsupported/thehive/1.1.0/docker-compose.yml diff --git a/thehive/1.1.0/env.txt b/unsupported/thehive/1.1.0/env.txt similarity index 100% rename from thehive/1.1.0/env.txt rename to unsupported/thehive/1.1.0/env.txt diff --git a/thehive/1.1.0/requirements.txt b/unsupported/thehive/1.1.0/requirements.txt similarity index 100% rename from thehive/1.1.0/requirements.txt rename to unsupported/thehive/1.1.0/requirements.txt diff --git a/thehive/1.1.0/run b/unsupported/thehive/1.1.0/run similarity index 100% rename from thehive/1.1.0/run rename to unsupported/thehive/1.1.0/run diff --git a/thehive/1.1.0/src/app.py b/unsupported/thehive/1.1.0/src/app.py similarity index 100% rename from thehive/1.1.0/src/app.py rename to unsupported/thehive/1.1.0/src/app.py diff --git a/thehive/1.1.2/Dockerfile b/unsupported/thehive/1.1.1/Dockerfile similarity index 100% rename from thehive/1.1.2/Dockerfile rename to unsupported/thehive/1.1.1/Dockerfile diff --git a/thehive/1.1.1/api.yaml b/unsupported/thehive/1.1.1/api.yaml similarity index 100% rename from thehive/1.1.1/api.yaml rename to unsupported/thehive/1.1.1/api.yaml diff --git a/thehive/1.1.1/docker-compose.yml b/unsupported/thehive/1.1.1/docker-compose.yml similarity index 100% rename from thehive/1.1.1/docker-compose.yml rename to unsupported/thehive/1.1.1/docker-compose.yml diff --git a/thehive/1.1.1/env.txt b/unsupported/thehive/1.1.1/env.txt similarity index 100% rename from thehive/1.1.1/env.txt rename to unsupported/thehive/1.1.1/env.txt diff --git a/thehive/1.1.1/requirements.txt b/unsupported/thehive/1.1.1/requirements.txt similarity index 100% rename from thehive/1.1.1/requirements.txt rename to unsupported/thehive/1.1.1/requirements.txt diff --git a/thehive/1.1.1/run b/unsupported/thehive/1.1.1/run similarity index 100% rename from thehive/1.1.1/run rename to unsupported/thehive/1.1.1/run diff --git a/thehive/1.1.1/src/app.py b/unsupported/thehive/1.1.1/src/app.py similarity index 100% rename from thehive/1.1.1/src/app.py rename to unsupported/thehive/1.1.1/src/app.py diff --git a/thehive/1.1.3/Dockerfile b/unsupported/thehive/1.1.2/Dockerfile similarity index 100% rename from thehive/1.1.3/Dockerfile rename to unsupported/thehive/1.1.2/Dockerfile diff --git a/thehive/1.1.2/api.yaml b/unsupported/thehive/1.1.2/api.yaml similarity index 100% rename from thehive/1.1.2/api.yaml rename to unsupported/thehive/1.1.2/api.yaml diff --git a/thehive/1.1.2/docker-compose.yml b/unsupported/thehive/1.1.2/docker-compose.yml similarity index 100% rename from thehive/1.1.2/docker-compose.yml rename to unsupported/thehive/1.1.2/docker-compose.yml diff --git a/thehive/1.1.2/env.txt b/unsupported/thehive/1.1.2/env.txt similarity index 100% rename from thehive/1.1.2/env.txt rename to unsupported/thehive/1.1.2/env.txt diff --git a/thehive/1.1.2/requirements.txt b/unsupported/thehive/1.1.2/requirements.txt similarity index 100% rename from thehive/1.1.2/requirements.txt rename to unsupported/thehive/1.1.2/requirements.txt diff --git a/thehive/1.1.2/run b/unsupported/thehive/1.1.2/run similarity index 100% rename from thehive/1.1.2/run rename to unsupported/thehive/1.1.2/run diff --git a/thehive/1.1.2/src/app.py b/unsupported/thehive/1.1.2/src/app.py similarity index 100% rename from thehive/1.1.2/src/app.py rename to unsupported/thehive/1.1.2/src/app.py diff --git a/unsupported/thehive/1.1.3/Dockerfile b/unsupported/thehive/1.1.3/Dockerfile new file mode 100644 index 00000000..bfa83edc --- /dev/null +++ b/unsupported/thehive/1.1.3/Dockerfile @@ -0,0 +1,26 @@ +# Base our app image off of the WALKOFF App SDK image +FROM frikky/shuffle:app_sdk as base + +# We're going to stage away all of the bloat from the build tools so lets create a builder stage +FROM base as builder + +# Install all alpine build tools needed for our pip installs +RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev + +# Install all of our pip packages in a single directory that we can copy to our base image later +RUN mkdir /install +WORKDIR /install +COPY requirements.txt /requirements.txt +RUN pip install --prefix="/install" -r /requirements.txt + +# Switch back to our base image and copy in all of our built packages and source code +FROM base +COPY --from=builder /install /usr/local +COPY src /app + +# Install any binary dependencies needed in our final image +RUN apk --no-cache add --update libmagic + +# Finally, lets run our app! +WORKDIR /app +CMD python app.py --log-level DEBUG diff --git a/thehive/1.1.3/api.yaml b/unsupported/thehive/1.1.3/api.yaml similarity index 100% rename from thehive/1.1.3/api.yaml rename to unsupported/thehive/1.1.3/api.yaml diff --git a/thehive/1.1.3/docker-compose.yml b/unsupported/thehive/1.1.3/docker-compose.yml similarity index 100% rename from thehive/1.1.3/docker-compose.yml rename to unsupported/thehive/1.1.3/docker-compose.yml diff --git a/thehive/1.1.3/env.txt b/unsupported/thehive/1.1.3/env.txt similarity index 100% rename from thehive/1.1.3/env.txt rename to unsupported/thehive/1.1.3/env.txt diff --git a/thehive/1.1.3/requirements.txt b/unsupported/thehive/1.1.3/requirements.txt similarity index 100% rename from thehive/1.1.3/requirements.txt rename to unsupported/thehive/1.1.3/requirements.txt diff --git a/thehive/1.1.3/run b/unsupported/thehive/1.1.3/run similarity index 100% rename from thehive/1.1.3/run rename to unsupported/thehive/1.1.3/run diff --git a/thehive/1.1.3/src/app.py b/unsupported/thehive/1.1.3/src/app.py similarity index 100% rename from thehive/1.1.3/src/app.py rename to unsupported/thehive/1.1.3/src/app.py diff --git a/thehive/README.md b/unsupported/thehive/README.md similarity index 100% rename from thehive/README.md rename to unsupported/thehive/README.md diff --git a/thehive/conf.png b/unsupported/thehive/conf.png similarity index 100% rename from thehive/conf.png rename to unsupported/thehive/conf.png diff --git a/unsupported/twitter/1.0.0/Dockerfile b/unsupported/twitter/1.0.0/Dockerfile new file mode 100644 index 00000000..364e1531 --- /dev/null +++ b/unsupported/twitter/1.0.0/Dockerfile @@ -0,0 +1,26 @@ +# Base our app image off of the WALKOFF App SDK image +FROM frikky/shuffle:app_sdk as base + +# We're going to stage away all of the bloat from the build tools so lets create a builder stage +FROM base as builder + +# Install all alpine build tools needed for our pip installs +RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev + +# Install all of our pip packages in a single directory that we can copy to our base image later +RUN mkdir /install +WORKDIR /install +COPY requirements.txt /requirements.txt +RUN pip install --prefix="/install" -r /requirements.txt + +# Switch back to our base image and copy in all of our built packages and source code +FROM base +COPY --from=builder /install /usr/local +COPY src /app + +# Install any binary dependencies needed in our final image +# RUN apk --no-cache add --update my_binary_dependency + +# Finally, lets run our app! +WORKDIR /app +CMD python app.py --log-level DEBUG diff --git a/twitter/1.0.0/api.yaml b/unsupported/twitter/1.0.0/api.yaml similarity index 100% rename from twitter/1.0.0/api.yaml rename to unsupported/twitter/1.0.0/api.yaml diff --git a/twitter/1.0.0/requirements.txt b/unsupported/twitter/1.0.0/requirements.txt similarity index 100% rename from twitter/1.0.0/requirements.txt rename to unsupported/twitter/1.0.0/requirements.txt diff --git a/twitter/1.0.0/src/app.py b/unsupported/twitter/1.0.0/src/app.py similarity index 100% rename from twitter/1.0.0/src/app.py rename to unsupported/twitter/1.0.0/src/app.py diff --git a/unsupported/vulndb/1.0.0/Dockerfile b/unsupported/vulndb/1.0.0/Dockerfile new file mode 100644 index 00000000..364e1531 --- /dev/null +++ b/unsupported/vulndb/1.0.0/Dockerfile @@ -0,0 +1,26 @@ +# Base our app image off of the WALKOFF App SDK image +FROM frikky/shuffle:app_sdk as base + +# We're going to stage away all of the bloat from the build tools so lets create a builder stage +FROM base as builder + +# Install all alpine build tools needed for our pip installs +RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev + +# Install all of our pip packages in a single directory that we can copy to our base image later +RUN mkdir /install +WORKDIR /install +COPY requirements.txt /requirements.txt +RUN pip install --prefix="/install" -r /requirements.txt + +# Switch back to our base image and copy in all of our built packages and source code +FROM base +COPY --from=builder /install /usr/local +COPY src /app + +# Install any binary dependencies needed in our final image +# RUN apk --no-cache add --update my_binary_dependency + +# Finally, lets run our app! +WORKDIR /app +CMD python app.py --log-level DEBUG diff --git a/vulndb/1.0.0/api.yaml b/unsupported/vulndb/1.0.0/api.yaml similarity index 100% rename from vulndb/1.0.0/api.yaml rename to unsupported/vulndb/1.0.0/api.yaml diff --git a/vulndb/1.0.0/docs.md b/unsupported/vulndb/1.0.0/docs.md similarity index 100% rename from vulndb/1.0.0/docs.md rename to unsupported/vulndb/1.0.0/docs.md diff --git a/vulndb/1.0.0/requirements.txt b/unsupported/vulndb/1.0.0/requirements.txt similarity index 100% rename from vulndb/1.0.0/requirements.txt rename to unsupported/vulndb/1.0.0/requirements.txt diff --git a/vulndb/1.0.0/shield-vulndb.svg b/unsupported/vulndb/1.0.0/shield-vulndb.svg similarity index 100% rename from vulndb/1.0.0/shield-vulndb.svg rename to unsupported/vulndb/1.0.0/shield-vulndb.svg diff --git a/vulndb/1.0.0/src/app.py b/unsupported/vulndb/1.0.0/src/app.py similarity index 100% rename from vulndb/1.0.0/src/app.py rename to unsupported/vulndb/1.0.0/src/app.py From e96348cb9a4a3251d750b1fe7a66235d0535d022 Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 30 Sep 2024 21:18:36 +0200 Subject: [PATCH 135/259] frikky -> shuffle changes --- README.md | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 8d19233d..04ed8a33 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,28 @@ # Shuffle Apps -All public apps are available in the search, engine either in your local instance or on [https://shuffler.io/search?tab=apps](https://shuffler.io/search?tab=apps). This is a repository for apps to be used in [Shuffle](https://github.com/frikky/shuffle) +All public apps are available in the search, engine either in your local instance or on [https://shuffler.io/search?tab=apps](https://shuffler.io/search?tab=apps). This is a repository for apps to be used in [Shuffle](https://github.com/shuffle/shuffle) -**PS:** These apps should be valid with WALKOFF (from NSA), but the SDK is different, meaning you have to change the FIRST line in each Dockerfile (FROM frikky/shuffle:app_sdk) to make it compatible with Shuffle. +**PS:** These apps should be valid with WALKOFF (from NSA), but the SDK is different, meaning you have to change the FIRST line in each Dockerfile (FROM shuffle/shuffle:app_sdk) to make it compatible with Shuffle. ## App Creation App creation can be done with the Shuffle App Creator (exports as OpenAPI) or Python, which makes it possible to connect _literally_ any tool. Always prioritize using the App Creator when applicable. -![Shuffle-workflow-categories](https://github.com/frikky/shuffle-workflows/blob/master/images/categories_circle_dark.png) +![Shuffle-workflow-categories](https://github.com/shuffle/shuffle-workflows/blob/master/images/categories_circle_dark.png) ### References -* [App Development Process](https://github.com/frikky/shuffle-docs/blob/master/handbook/engineering/app_development.md) +* [App Development Process](https://github.com/shuffle/shuffle-docs/blob/master/handbook/engineering/app_development.md) * [Python app documentation](https://shuffler.io/docs/app_creation) -* [Apps in progress](https://github.com/frikky/Shuffle-apps/projects/1) +* [Apps in progress](https://github.com/shuffle/shuffle-apps/projects/1) ### Categories We have defined eight (8) "major" categories of tools that are necessary to any cybersecurity threat. Most security-related tools can fit into one of these eight. -1. [Communication](https://github.com/frikky/Shuffle-apps/issues/26) - Any way to chat; WhatsApp, SMS, Email etc. -2. [Case Management](https://github.com/frikky/Shuffle-apps/issues/22) - The central hub for operation teams. -3. [SIEM](https://github.com/frikky/Shuffle-apps/issues/21) - Search engine for logs in an enterprise. Used to find evil. -4. [Assets](https://github.com/frikky/Shuffle-apps/issues/25) - Discover endpoint information. Vulnerabilities, owners, departments etc. -5. [IAM](https://github.com/frikky/Shuffle-apps/issues/86) - Access Management. Active Directory, Google Workspaces, Single Sign-on etc. -6. [Intelligence](https://github.com/frikky/Shuffle-apps/issues/24) - Typically a vendor explaining what you should be looking for. -7. [Network](https://github.com/frikky/Shuffle-apps/issues/27) - Anything BETWEEN your connected devices. Firewalls, WAF, Switches, Bluetooth... -8. [Eradication](https://github.com/frikky/Shuffle-apps/issues/23) - Control machines directly to eradicate evil. Hard and undefined (EDR & AV) +1. [Communication](https://github.com/shuffle/shuffle-apps/issues/26) - Any way to chat; WhatsApp, SMS, Email etc. +2. [Case Management](https://github.com/shuffle/shuffle-apps/issues/22) - The central hub for operation teams. +3. [SIEM](https://github.com/shuffle/shuffle-apps/issues/21) - Search engine for logs in an enterprise. Used to find evil. +4. [Assets](https://github.com/shuffle/shuffle-apps/issues/25) - Discover endpoint information. Vulnerabilities, owners, departments etc. +5. [IAM](https://github.com/shuffle/shuffle-apps/issues/86) - Access Management. Active Directory, Google Workspaces, Single Sign-on etc. +6. [Intelligence](https://github.com/shuffle/shuffle-apps/issues/24) - Typically a vendor explaining what you should be looking for. +7. [Network](https://github.com/shuffle/shuffle-apps/issues/27) - Anything BETWEEN your connected devices. Firewalls, WAF, Switches, Bluetooth... +8. [Eradication](https://github.com/shuffle/shuffle-apps/issues/23) - Control machines directly to eradicate evil. Hard and undefined (EDR & AV) ## OpenAPI Apps in this repository are mostly manually made. Shuffle is striving for standardization and accessability, and our effort is focused on OpenAPI rather than manual work. With this in mind, most app creation that supports REST API's will be continued here. @@ -32,18 +32,18 @@ Apps in this repository are mostly manually made. Shuffle is striving for standa ## Support * [Discord](https://discord.gg/B2CBzUm) * [Twitter](https://twitter.com/shuffleio) -* [Email](mailto:frikky@shuffler.io) -* [Open issue](https://github.com/frikky/Shuffle/issues/new) +* [Email](mailto:support@shuffler.io) +* [Open issue](https://github.com/shuffle/shuffle/issues/new) * [Shuffler.io](https://shuffler.io/contact) ## External contributions -[**App magicians**](https://github.com/frikky/shuffle-apps) - - +[**App magicians**](https://github.com/shuffle/shuffle-apps) + + [**OpenAPI creators**](https://github.com/frikky/security-openapis) - + @@ -51,4 +51,4 @@ Apps in this repository are mostly manually made. Shuffle is striving for standa All apps, workflows and modular parts of Shuffle including our App SDK is under licensed under MIT, meaning you can freely use it anywhere in any way you want. # Contributing -Contributing guidelines for outlined [here](https://github.com/frikky/Shuffle/blob/master/.github/CONTRIBUTING.md). +Contributing guidelines for outlined [here](https://github.com/shuffle/shuffle/blob/master/.github/CONTRIBUTING.md). From dfa21f23b3779eef047af8a45de238bd52c4dcaf Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 2 Oct 2024 17:24:57 +0200 Subject: [PATCH 136/259] Shuffle-AI mods --- shuffle-ai/1.0.0/Dockerfile | 2 +- shuffle-ai/1.0.0/src/app.py | 21 +++++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/shuffle-ai/1.0.0/Dockerfile b/shuffle-ai/1.0.0/Dockerfile index 36f0f4d0..b4362193 100644 --- a/shuffle-ai/1.0.0/Dockerfile +++ b/shuffle-ai/1.0.0/Dockerfile @@ -5,7 +5,7 @@ FROM frikky/shuffle:app_sdk as base FROM base as builder # Install all alpine build tools needed for our pip installs -RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev git +RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev git poppler-utils # Install all of our pip packages in a single directory that we can copy to our base image later RUN mkdir /install diff --git a/shuffle-ai/1.0.0/src/app.py b/shuffle-ai/1.0.0/src/app.py index 979a0158..3c48d2ee 100644 --- a/shuffle-ai/1.0.0/src/app.py +++ b/shuffle-ai/1.0.0/src/app.py @@ -135,6 +135,11 @@ def export_text_to_json(image_text, extracted_text): "reason": "Something failed in reading and parsing the pdf. See error logs for more info", } + # Check type of pdf_data["data"] + if not isinstance(pdf_data["data"], bytes): + self.logger.info("Encoding data to bytes for the bytestream reader") + pdf_data["data"] = pdf_data["data"].encode() + # Make a tempfile for the file data from self.get_file # Make a tempfile with tempfile library with tempfile.NamedTemporaryFile() as temp: @@ -162,12 +167,24 @@ def export_text_to_json(image_text, extracted_text): def extract_text_from_image(self, file_id): # Check if it's a pdf + + pdf_data = self.get_file(file_id) + if "filename" not in pdf_data: + available_fields = [] + for key, value in pdf_data.items(): + available_fields.append(key) + + return { + "success": False, + "reason": "File not found", + "details": f"Available fields: {available_fields}", + } + # If it is, use extract_text_from_pdf # If it's not, use pytesseract - if self.get_file(file_id)["name"].endswith(".pdf"): + if pdf_data["filename"].endswith(".pdf"): return self.extract_text_from_pdf(file_id) - pdf_data = self.get_file(file_id) defaultdata = { "success": False, "file_id": file_id, From 38a1bda5eb186ff8c43efdc3d37669bd58de216e Mon Sep 17 00:00:00 2001 From: Frikky Date: Sat, 5 Oct 2024 00:01:20 +0200 Subject: [PATCH 137/259] Added sample condition handler with IF/ELSE --- shuffle-tools/1.2.0/api.yaml | 13 ++ shuffle-tools/1.2.0/src/app.py | 23 +++- shuffle-tools/1.2.0/src/switch.py | 203 ++++++++++++++++++++++++++++++ 3 files changed, 238 insertions(+), 1 deletion(-) create mode 100644 shuffle-tools/1.2.0/src/switch.py diff --git a/shuffle-tools/1.2.0/api.yaml b/shuffle-tools/1.2.0/api.yaml index d075ddc9..ff6162b6 100644 --- a/shuffle-tools/1.2.0/api.yaml +++ b/shuffle-tools/1.2.0/api.yaml @@ -1203,6 +1203,19 @@ actions: example: 'ls -la' schema: type: string + - name: if_else_routing + description: Routes based on if-else statements + parameters: + - name: conditions + description: The conditions to be met + required: true + multiline: true + example: "REPEATING: Hello world" + schema: + type: string + returns: + schema: + type: string #- name: parse_ioc_new # description: Parse IOC's based on https://github.com/fhightower/ioc-finder # parameters: diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index cc713f91..a2e0b62a 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -2787,7 +2787,28 @@ def list_cidr_ips(self, cidr): } return returnvalue - + + def if_else_routing(self, conditions): + # True by default + to_return = { + "success": True, + "run_else": True, + } + + if len(conditions) == 0: + conditions = [] + + for condition in conditions: + pass + + # Loop conditions + # Return them without a loop to make it EASY to understand + # Validation should be: + # Continuation based on .id.valid + # .valid -> true/false + # If no id exists, use name? + + return to_return if __name__ == "__main__": Tools.run() diff --git a/shuffle-tools/1.2.0/src/switch.py b/shuffle-tools/1.2.0/src/switch.py new file mode 100644 index 00000000..5d413a9a --- /dev/null +++ b/shuffle-tools/1.2.0/src/switch.py @@ -0,0 +1,203 @@ +# self, sourcevalue, condition, destinationvalue +def run_validation(sourcevalue, check, destinationvalue): + if check == "=" or check == "==" or check.lower() == "equals": + if str(sourcevalue).lower() == str(destinationvalue).lower(): + return True + elif check == "!=" or check.lower() == "does not equal": + if str(sourcevalue).lower() != str(destinationvalue).lower(): + return True + elif check.lower() == "startswith": + if str(sourcevalue).lower().startswith(str(destinationvalue).lower()): + return True + + + elif check.lower() == "endswith": + if str(sourcevalue).lower().endswith(str(destinationvalue).lower()): + return True + elif check.lower() == "contains": + if destinationvalue.lower() in sourcevalue.lower(): + return True + + elif check.lower() == "is empty" or check.lower() == "is_empty": + try: + if len(json.loads(sourcevalue)) == 0: + return True + except Exception as e: + print("[ERROR] Failed to check if empty as list: {e}") + + if len(str(sourcevalue)) == 0: + return True + + elif check.lower() == "contains_any_of": + newvalue = [destinationvalue.lower()] + if "," in destinationvalue: + newvalue = destinationvalue.split(",") + elif ", " in destinationvalue: + newvalue = destinationvalue.split(", ") + + for item in newvalue: + if not item: + continue + + if item.strip() in sourcevalue: + return True + + elif check.lower() == "larger than" or check.lower() == "bigger than" or check == ">" or check == ">=": + try: + if str(sourcevalue).isdigit() and str(destinationvalue).isdigit(): + if int(sourcevalue) > int(destinationvalue): + return True + + except AttributeError as e: + print("[WARNING] Condition larger than failed with values %s and %s: %s" % (sourcevalue, destinationvalue, e)) + + try: + destinationvalue = len(json.loads(destinationvalue)) + except Exception as e: + print("[WARNING] Failed to convert destination to list: {e}") + try: + # Check if it's a list in autocast and if so, check the length + if len(json.loads(sourcevalue)) > int(destinationvalue): + return True + except Exception as e: + print("[WARNING] Failed to check if larger than as list: {e}") + + + elif check.lower() == "smaller than" or check.lower() == "less than" or check == "<" or check == "<=": + print("In smaller than check: %s %s" % (sourcevalue, destinationvalue)) + + try: + if str(sourcevalue).isdigit() and str(destinationvalue).isdigit(): + if int(sourcevalue) < int(destinationvalue): + return True + + except AttributeError as e: + pass + + try: + destinationvalue = len(json.loads(destinationvalue)) + except Exception as e: + print("[WARNING] Failed to convert destination to list: {e}") + + try: + # Check if it's a list in autocast and if so, check the length + if len(json.loads(sourcevalue)) < int(destinationvalue): + return True + except Exception as e: + print("[WARNING] Failed to check if smaller than as list: {e}") + + elif check.lower() == "re" or check.lower() == "matches regex": + try: + found = re.search(str(destinationvalue), str(sourcevalue)) + except re.error as e: + return False + except Exception as e: + return False + + if found == None: + return False + + return True + else: + print("[DEBUG] Condition: can't handle %s yet. Setting to true" % check) + + return False + +def evaluate_conditions(condition_structure): + operator = condition_structure.get('operator') + + # Base case: Single condition + if 'source' in condition_structure: + source = condition_structure['source'] + condition = condition_structure['condition'] + destination = condition_structure['destination'] + + # self. + return run_validation(source, condition, destination) + + # Recursive case: Logical operator + elif operator == "AND": + return all(evaluate_conditions(sub_condition) for sub_condition in condition_structure['conditions']) + + elif operator == "OR": + return any(evaluate_conditions(sub_condition) for sub_condition in condition_structure['conditions']) + + elif operator == "NOT": + return not evaluate_conditions(condition_structure['conditions'][0]) + + else: + raise ValueError(f"Unknown operator: {operator}") + + +def switch(conditions): + to_return = { + "success": True, + "run_else": True, + } + + for condition in conditions: + if "id" not in condition: + print("Condition ID not found") + continue + + evaluated = False + try: + evaluated = evaluate_conditions(condition) + except Exception as e: + print(f"Failed to evaluate condition {condition['id']}: {e}") + + if evaluated == True: + to_return["run_else"] = False + + to_return[condition["id"]] = evaluated + + return to_return + +# Example usage + +condition_structure = { + "id": "lol", + "operator": "AND", + "conditions": [ + { # true + "source": "20", # age + "condition": ">", + "destination": 18 + }, + { # true + "operator": "OR", + "conditions": [ + { + "source": "active", # status + "condition": "==", + "destination": "active" + }, + { + "source": "1500", # balance + "condition": ">=", + "destination": 1000 + } + ] + }, + { + "operator": "NOT", + "conditions": [ + { + "source": "user", # user + "condition": "==", + "destination": "admin" + } + ] + } + ] +} + +newcondition = condition_structure.copy() +testconditions = [condition_structure] +newcondition['id'] = "lol2" +testconditions.append(newcondition) + +result = switch(testconditions) +print() +print() +print("Output: ", result) From a1170df7581cfaaf44b2e68efc3e948d4922b163 Mon Sep 17 00:00:00 2001 From: Frikky Date: Tue, 22 Oct 2024 14:12:36 +0200 Subject: [PATCH 138/259] Revert 1.2.1 shuffle tools --- shuffle-tools/1.2.0/src/app.py | 6 +++++- shuffle-tools/1.2.0/src/switch.py | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index a2e0b62a..a03d2447 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -2788,7 +2788,11 @@ def list_cidr_ips(self, cidr): return returnvalue - def if_else_routing(self, conditions): + def switch(self, conditions): + # Check if conditions is a list or not + if not isinstance(conditions, list): + conditions = [conditions] + # True by default to_return = { "success": True, diff --git a/shuffle-tools/1.2.0/src/switch.py b/shuffle-tools/1.2.0/src/switch.py index 5d413a9a..78ede505 100644 --- a/shuffle-tools/1.2.0/src/switch.py +++ b/shuffle-tools/1.2.0/src/switch.py @@ -1,5 +1,5 @@ # self, sourcevalue, condition, destinationvalue -def run_validation(sourcevalue, check, destinationvalue): +def validate_condition(sourcevalue, check, destinationvalue): if check == "=" or check == "==" or check.lower() == "equals": if str(sourcevalue).lower() == str(destinationvalue).lower(): return True @@ -113,7 +113,7 @@ def evaluate_conditions(condition_structure): destination = condition_structure['destination'] # self. - return run_validation(source, condition, destination) + return validate_condition(source, condition, destination) # Recursive case: Logical operator elif operator == "AND": From 11a6f7a5e8a69beeec9379b62483eb5e47a9bf30 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Wed, 30 Oct 2024 15:11:28 +0530 Subject: [PATCH 139/259] chore: updating json2xml to 5.0.5 --- shuffle-tools/1.0.0/requirements.txt | 2 +- shuffle-tools/1.1.0/requirements.txt | 2 +- shuffle-tools/1.2.0/requirements.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/shuffle-tools/1.0.0/requirements.txt b/shuffle-tools/1.0.0/requirements.txt index 1ac4003c..a81ba787 100644 --- a/shuffle-tools/1.0.0/requirements.txt +++ b/shuffle-tools/1.0.0/requirements.txt @@ -4,5 +4,5 @@ rarfile==4.0 pyminizip==0.2.4 requests==2.25.1 xmltodict==0.11.0 -json2xml==3.6.0 +json2xml==5.0.5 ipaddress==1.0.23 diff --git a/shuffle-tools/1.1.0/requirements.txt b/shuffle-tools/1.1.0/requirements.txt index 1ac4003c..a81ba787 100644 --- a/shuffle-tools/1.1.0/requirements.txt +++ b/shuffle-tools/1.1.0/requirements.txt @@ -4,5 +4,5 @@ rarfile==4.0 pyminizip==0.2.4 requests==2.25.1 xmltodict==0.11.0 -json2xml==3.6.0 +json2xml==5.0.5 ipaddress==1.0.23 diff --git a/shuffle-tools/1.2.0/requirements.txt b/shuffle-tools/1.2.0/requirements.txt index 1dc466c6..9c8ee043 100644 --- a/shuffle-tools/1.2.0/requirements.txt +++ b/shuffle-tools/1.2.0/requirements.txt @@ -4,7 +4,7 @@ rarfile==4.0 pyminizip==0.2.4 requests==2.25.1 xmltodict==0.11.0 -json2xml==3.6.0 +json2xml==5.0.5 ipaddress==1.0.23 google.auth==1.23.0 paramiko==3.1.0 From 9eed4fd35e7af9d4df8654bc189c4d3634c2d299 Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 31 Oct 2024 18:09:04 +0100 Subject: [PATCH 140/259] Fixed email --- email/1.3.0/api.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/email/1.3.0/api.yaml b/email/1.3.0/api.yaml index 23fdff8f..eca1cf76 100644 --- a/email/1.3.0/api.yaml +++ b/email/1.3.0/api.yaml @@ -24,7 +24,7 @@ actions: - name: recipients description: The recipients of the email multiline: false - example: "test@example.com,frikky@shuffler.io" + example: "test@example.com,support@shuffler.io" required: true schema: type: string @@ -51,7 +51,7 @@ actions: - name: username description: The SMTP login username multiline: false - example: "frikky@shuffler.io" + example: "support@shuffler.io" required: false schema: type: string @@ -79,14 +79,14 @@ actions: - name: recipient description: The receiver(s) of the email multiline: false - example: "frikky@shuffler.io,frikky@shuffler.io" + example: "support@shuffler.io,test@shuffler.io" required: true schema: type: string - name: cc_emails description: cc_emails multiline: false - example: "frikky@shuffler.io,frikky@shuffler.io" + example: "support@shuffler.io,test@shuffler.io" required: false schema: type: string @@ -138,7 +138,7 @@ actions: - name: username description: The SMTP login username multiline: false - example: "frikky@shuffler.io" + example: "support@shuffler.io" required: true schema: type: string From 7a4e5ab89a48daaabf78d5e11faf567e924f5c29 Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 31 Oct 2024 19:05:28 +0100 Subject: [PATCH 141/259] Added verbosity to SMTP send --- email/1.3.0/src/app.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/email/1.3.0/src/app.py b/email/1.3.0/src/app.py index 6e1b5859..588863dc 100644 --- a/email/1.3.0/src/app.py +++ b/email/1.3.0/src/app.py @@ -74,15 +74,15 @@ def send_email_shuffle(self, apikey, recipients, subject, body): headers = {"Authorization": "Bearer %s" % apikey} return requests.post(url, headers=headers, json=data).text - def send_email_smtp( - self, smtp_host, recipient, subject, body, smtp_port, attachments="", username="", password="", ssl_verify="True", body_type="html", cc_emails="" - ): + def send_email_smtp(self, smtp_host, recipient, subject, body, smtp_port, attachments="", username="", password="", ssl_verify="True", body_type="html", cc_emails=""): + self.logger.info("Sending email to %s with subject %s" % (recipient, subject)) if type(smtp_port) == str: try: smtp_port = int(smtp_port) except ValueError: return "SMTP port needs to be a number (Current: %s)" % smtp_port + self.logger.info("Pre SMTP setup") try: s = smtplib.SMTP(host=smtp_host, port=smtp_port) except socket.gaierror as e: @@ -95,6 +95,7 @@ def send_email_smtp( else: s.starttls() + self.logger.info("Pre SMTP auth") if len(username) > 1 or len(password) > 1: try: s.login(username, password) @@ -108,6 +109,7 @@ def send_email_smtp( body_type = "html" # setup the parameters of the message + self.logger.info("Pre mime multipart") msg = MIMEMultipart() msg["From"] = username msg["To"] = recipient @@ -116,10 +118,12 @@ def send_email_smtp( if cc_emails != None and len(cc_emails) > 0: msg["Cc"] = cc_emails + self.logger.info("Pre mime check") msg.attach(MIMEText(body, body_type)) # Read the attachments attachment_count = 0 + self.logger.info("Pre attachments") try: if attachments != None and len(attachments) > 0: print("Got attachments: %s" % attachments) @@ -153,7 +157,7 @@ def send_email_smtp( except Exception as e: self.logger.info(f"Error in attachment parsing for email: {e}") - + self.logger.info("Pre send msg") try: s.send_message(msg) except smtplib.SMTPDataError as e: From ab31917d3d3b1a9a580f0f5e1b04b593a0ec8f4e Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 15 Nov 2024 13:09:42 +0100 Subject: [PATCH 142/259] Fixed subflow to use /forms/X instead of /run urls --- oauth2-example/1.0.0/Dockerfile | 26 ----------- oauth2-example/1.0.0/api.yaml | 53 --------------------- oauth2-example/1.0.0/docker-compose.yml | 14 ------ oauth2-example/1.0.0/env.txt | 4 -- oauth2-example/1.0.0/requirements.txt | 3 -- oauth2-example/1.0.0/run | 17 ------- oauth2-example/1.0.0/src/app.py | 61 ------------------------- shuffle-subflow/1.0.0/src/app.py | 4 +- shuffle-subflow/1.1.0/src/app.py | 6 +-- shuffle-tools/1.2.0/src/app.py | 3 +- 10 files changed, 7 insertions(+), 184 deletions(-) delete mode 100644 oauth2-example/1.0.0/Dockerfile delete mode 100644 oauth2-example/1.0.0/api.yaml delete mode 100644 oauth2-example/1.0.0/docker-compose.yml delete mode 100644 oauth2-example/1.0.0/env.txt delete mode 100644 oauth2-example/1.0.0/requirements.txt delete mode 100644 oauth2-example/1.0.0/run delete mode 100644 oauth2-example/1.0.0/src/app.py diff --git a/oauth2-example/1.0.0/Dockerfile b/oauth2-example/1.0.0/Dockerfile deleted file mode 100644 index bfa83edc..00000000 --- a/oauth2-example/1.0.0/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -# Base our app image off of the WALKOFF App SDK image -FROM frikky/shuffle:app_sdk as base - -# We're going to stage away all of the bloat from the build tools so lets create a builder stage -FROM base as builder - -# Install all alpine build tools needed for our pip installs -RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev - -# Install all of our pip packages in a single directory that we can copy to our base image later -RUN mkdir /install -WORKDIR /install -COPY requirements.txt /requirements.txt -RUN pip install --prefix="/install" -r /requirements.txt - -# Switch back to our base image and copy in all of our built packages and source code -FROM base -COPY --from=builder /install /usr/local -COPY src /app - -# Install any binary dependencies needed in our final image -RUN apk --no-cache add --update libmagic - -# Finally, lets run our app! -WORKDIR /app -CMD python app.py --log-level DEBUG diff --git a/oauth2-example/1.0.0/api.yaml b/oauth2-example/1.0.0/api.yaml deleted file mode 100644 index 40c44127..00000000 --- a/oauth2-example/1.0.0/api.yaml +++ /dev/null @@ -1,53 +0,0 @@ -walkoff_version: 1.0.0 -app_version: 1.0.0 -name: oauth2-example -description: Oauth2 sample -tags: - - Example -categories: - - Example -contact_info: - name: "@frikkylikeme" - url: https://github.com/frikky -authentication: - required: true - type: oauth2 - redirect_uri: "https://login.microsoftonline.com/common/oauth2/authorize" - token_uri: "https://login.microsoftonline.com/common/oauth2/v2.0/token" - client_id: "dae24316-4bec-4832-b660-4cba6dc2477b" - client_secret: "._Qu3EvYY-OW_D57uy79qwEo.32qD6.l0z" - scope: - - UserAuthenticationMethod.ReadWrite.All -actions: - - name: reset_password - description: Change password of a user in Azure - parameters: - - name: userId - description: - example: "user@company.com" - required: true - schema: - type: string - - name: passwordId - description: - example: "28c10230-6103-485e-b985-444c60001490" - required: true - schema: - type: string - - name: newPassword - description: - example: "*****" - required: false - schema: - type: string - returns: - example: '{"data": "this is a test", "this_is_a_number": 1, "this_is_a_list": [{"item": [{"hello": "there", "how_is_this": {"sub_in_sub": [{"another": "list"}]}}]}, {"item": "2"}], "subobject": {"data": "subobject"}}' - schema: - type: string - - name: get_password_methods - description: Get available password methods for your user - returns: - example: '{"data": "this is a test", "this_is_a_number": 1, "this_is_a_list": [{"item": [{"hello": "there", "how_is_this": {"sub_in_sub": [{"another": "list"}]}}]}, {"item": "2"}], "subobject": {"data": "subobject"}}' - schema: - type: string -large_image: data:image/png;base64,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 diff --git a/oauth2-example/1.0.0/docker-compose.yml b/oauth2-example/1.0.0/docker-compose.yml deleted file mode 100644 index 47de05b2..00000000 --- a/oauth2-example/1.0.0/docker-compose.yml +++ /dev/null @@ -1,14 +0,0 @@ -version: '3.4' -services: - thehive: - build: - context: . - dockerfile: Dockerfile - env_file: - - env.txt - restart: "no" - deploy: - mode: replicated - replicas: 10 - restart_policy: - condition: none diff --git a/oauth2-example/1.0.0/env.txt b/oauth2-example/1.0.0/env.txt deleted file mode 100644 index 1398a35f..00000000 --- a/oauth2-example/1.0.0/env.txt +++ /dev/null @@ -1,4 +0,0 @@ -REDIS_URI=redis://redis -REDIS_ACTION_RESULT_CH=action-results -REDIS_ACTION_RESULTS_GROUP=action-results-group -APP_NAME=thehive diff --git a/oauth2-example/1.0.0/requirements.txt b/oauth2-example/1.0.0/requirements.txt deleted file mode 100644 index 1d40c46a..00000000 --- a/oauth2-example/1.0.0/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -requests==2.25.1 -thehive4py==1.8.1 -python-magic==0.4.18 diff --git a/oauth2-example/1.0.0/run b/oauth2-example/1.0.0/run deleted file mode 100644 index 6127bfb7..00000000 --- a/oauth2-example/1.0.0/run +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/sh -docker stop frikky/shuffle:thehive_1.0.0 --force -docker rm frikky/shuffle:thehive_1.0.0 --force -docker rmi frikky/shuffle:thehive_1.0.0 --force - -docker build . -t frikky/shuffle:thehive_1.0.0 - -echo "RUNNING!\n\n" -docker run \ - --env CALLBACK_URL="http://192.168.239.144:5001" \ - --env ACTION='{"app_name":"testing","app_version":"1.0.0","errors":[],"id_":"13fa4c3f-8991-3ade-b90d-f326fd4941dd","is_valid":true,"label":"random_number","environment":"onprem","name":"random_number","parameters":[],"position":{"x":178.07868996109607,"y":457.28345902971614},"priority":3}' \ - --env FUNCTION_APIKEY="asdasd" \ - --env EXECUTIONID="2349bf96-51ad-68d2-5ca6-75ef8f7ee814" \ - --env AUTHORIZATION="8e344a2e-db51-448f-804c-eb959a32c139" \ - frikky/shuffle:thehive_1.0.0 - -docker push frikky/shuffle:thehive_1.0.0 diff --git a/oauth2-example/1.0.0/src/app.py b/oauth2-example/1.0.0/src/app.py deleted file mode 100644 index e36997e4..00000000 --- a/oauth2-example/1.0.0/src/app.py +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import asyncio -import time -import random -import json -import requests -import thehive4py - -from thehive4py.api import TheHiveApi -from thehive4py.query import * -import thehive4py.models - -from walkoff_app_sdk.app_base import AppBase - - -class Oauth2Example(AppBase): - __version__ = "1.0.0" - app_name = "oauth2-example" - - def __init__(self, redis, logger, console_logger=None): - """ - Each app should have this __init__ to set up Redis and logging. - :param redis: - :param logger: - :param console_logger: - """ - super().__init__(redis, logger, console_logger) - - def authenticate(self, access_token, refresh_token): - s = requests.Session() - s.headers = { - "Content-Type": "application/json", - "Authorization": "Bearer %s" % access_token - } - - return s - - # UserAuthenticationMethod.ReadWrite.All - def reset_password(self, access_token, refresh_token, userId, passwordId, newPassword=""): - graph_url = "https://graph.microsoft.com" - session = self.authenticate(access_token, refresh_token) - - url = "https://graph.microsoft.com/beta/users/%s/authentication/passwordMethods/%s/resetPassword" % (userId, passwordId) - response = session.post(url) - print(response.status_code) - return response.text - - # UserAuthenticationMethod.ReadWrite.All - def get_password_methods(self, access_token, refresh_token): - graph_url = "https://graph.microsoft.com" - session = self.authenticate(access_token, refresh_token) - - url = "https://graph.microsoft.com/beta/me/authentication/passwordMethods" - response = session.get(url) - print(response.status_code) - return response.text - -if __name__ == "__main__": - Oauth2Example.run() diff --git a/shuffle-subflow/1.0.0/src/app.py b/shuffle-subflow/1.0.0/src/app.py index c6f25c41..a5ffebf3 100644 --- a/shuffle-subflow/1.0.0/src/app.py +++ b/shuffle-subflow/1.0.0/src/app.py @@ -77,8 +77,8 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" argument = json.dumps({ "information": information, "parent_workflow": self.full_execution["workflow"]["id"], - "frontend_continue": "%s/workflows/%s/run?authorization=%s&reference_execution=%s&answer=true" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"]), - "frontend_abort": "%s/workflows/%s/run?authorization=%s&reference_execution=%s&answer=false" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"]), + "frontend_continue": "%s/forms/%s?authorization=%s&reference_execution=%s&answer=true" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"]), + "frontend_abort": "%s/forms/%s?authorization=%s&reference_execution=%s&answer=false" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"]), "api_continue": "%s/api/v1/workflows/%s/execute?authorization=%s&reference_execution=%s&answer=true" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"]), "api_abort": "%s/api/v1/workflows/%s/execute?authorization=%s&reference_execution=%s&answer=false" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"]), }) diff --git a/shuffle-subflow/1.1.0/src/app.py b/shuffle-subflow/1.1.0/src/app.py index 71216808..9641cbd4 100644 --- a/shuffle-subflow/1.1.0/src/app.py +++ b/shuffle-subflow/1.1.0/src/app.py @@ -68,9 +68,9 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" if "shuffle-backend" in frontend_url: frontend_url = "" - explore_path = "%s/workflows/%s/run?authorization=%s&reference_execution=%s&source_node=%s" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"], source_node) - frontend_continue_url = "%s/workflows/%s/run?authorization=%s&reference_execution=%s&answer=true&source_node=%s" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"], source_node) - frontend_abort_url = "%s/workflows/%s/run?authorization=%s&reference_execution=%s&answer=false&source_node=%s" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"], source_node) + explore_path = "%s/forms/%s?authorization=%s&reference_execution=%s&source_node=%s" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"], source_node) + frontend_continue_url = "%s/forms/%s?authorization=%s&reference_execution=%s&answer=true&source_node=%s" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"], source_node) + frontend_abort_url = "%s/forms/%s?authorization=%s&reference_execution=%s&answer=false&source_node=%s" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"], source_node) api_continue_url = "%s/api/v1/workflows/%s/execute?authorization=%s&reference_execution=%s&answer=true&source_node=%s" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"], source_node) api_abort_url = "%s/api/v1/workflows/%s/execute?authorization=%s&reference_execution=%s&answer=false&source_node=%s" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"], source_node) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index a03d2447..0ae0cc4e 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -37,7 +37,8 @@ import concurrent.futures import multiprocessing -from walkoff_app_sdk.app_base import AppBase +#from walkoff_app_sdk.app_base import AppBase +from shuffle_sdk import AppBase class Tools(AppBase): __version__ = "1.2.0" From 67b16d476065d0b0de56d6d32699f224c812cce8 Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 18 Nov 2024 12:08:04 +0100 Subject: [PATCH 143/259] Rebuild with right sdk --- shuffle-tools/1.2.0/src/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 0ae0cc4e..593433a5 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -37,8 +37,8 @@ import concurrent.futures import multiprocessing -#from walkoff_app_sdk.app_base import AppBase -from shuffle_sdk import AppBase +from walkoff_app_sdk.app_base import AppBase +#from shuffle_sdk import AppBase class Tools(AppBase): __version__ = "1.2.0" From 4d9f0332d5e7432cbe040bdeb6a6d9d244050534 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Fri, 29 Nov 2024 17:52:34 +0530 Subject: [PATCH 144/259] feat[shuffle-tools-fork]: Shuffle tools fork --- shuffle-tools-fork/1.2.0/Dockerfile | 27 +++ shuffle-tools-fork/1.2.0/api.yaml | 27 +++ shuffle-tools-fork/1.2.0/docker-compose.yml | 15 ++ shuffle-tools-fork/1.2.0/requirements.txt | 11 ++ shuffle-tools-fork/1.2.0/run.sh | 11 ++ shuffle-tools-fork/1.2.0/src/app.py | 127 ++++++++++++ shuffle-tools-fork/1.2.0/src/concurrency.py | 201 +++++++++++++++++++ shuffle-tools-fork/1.2.0/src/switch.py | 203 ++++++++++++++++++++ 8 files changed, 622 insertions(+) create mode 100644 shuffle-tools-fork/1.2.0/Dockerfile create mode 100644 shuffle-tools-fork/1.2.0/api.yaml create mode 100644 shuffle-tools-fork/1.2.0/docker-compose.yml create mode 100644 shuffle-tools-fork/1.2.0/requirements.txt create mode 100644 shuffle-tools-fork/1.2.0/run.sh create mode 100644 shuffle-tools-fork/1.2.0/src/app.py create mode 100644 shuffle-tools-fork/1.2.0/src/concurrency.py create mode 100644 shuffle-tools-fork/1.2.0/src/switch.py diff --git a/shuffle-tools-fork/1.2.0/Dockerfile b/shuffle-tools-fork/1.2.0/Dockerfile new file mode 100644 index 00000000..5c1a8af4 --- /dev/null +++ b/shuffle-tools-fork/1.2.0/Dockerfile @@ -0,0 +1,27 @@ +# Base our app image off of the WALKOFF App SDK image +FROM frikky/shuffle:app_sdk as base + +# We're going to stage away all of the bloat from the build tools so lets create a builder stage +FROM base as builder + +# Install all alpine build tools needed for our pip installs +RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev git + +# Install all of our pip packages in a single directory that we can copy to our base image later +RUN mkdir /install +WORKDIR /install +COPY requirements.txt /requirements.txt +RUN pip install --no-cache-dir --prefix="/install" -r /requirements.txt + +# Switch back to our base image and copy in all of our built packages and source code +FROM base +COPY --from=builder /install /usr/local +COPY src /app + +# Install any binary dependencies needed in our final image +# RUN apk --no-cache add --update my_binary_dependency +RUN apk --no-cache add jq git curl + +# Finally, lets run our app! +WORKDIR /app +CMD ["python", "app.py", "--log-level", "DEBUG"] diff --git a/shuffle-tools-fork/1.2.0/api.yaml b/shuffle-tools-fork/1.2.0/api.yaml new file mode 100644 index 00000000..cc04349f --- /dev/null +++ b/shuffle-tools-fork/1.2.0/api.yaml @@ -0,0 +1,27 @@ +--- +app_version: 1.2.0 +name: Shuffle Tools Fork +description: A tool app for Shuffle. Gives access to most missing features along with Liquid. +tags: + - Testing + - Shuffle +categories: + - Other +contact_info: + name: "@frikkylikeme" + url: https://shuffler.io + email: frikky@shuffler.io +actions: + - name: execute_python + description: Runs python with the data input. Any prints will be returned. + parameters: + - name: code + description: The code to run. Can be a file ID from within Shuffle. + required: true + multiline: true + example: print("hello world") + schema: + type: string + +large_image: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAK4AAACuCAYAAACvDDbuAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QA/wD/AP+gvaeTAAAAB3RJTUUH5AgXDjM6hEZGWwAAD+lJREFUeNrtXb/vJTcRH7/v3iVBCqRBiCAQAtHwq4AWRElHwX8AoqbmXwDRpiH/QyQkGoogUSAhKIKUAE1IdSRSREhQQk7c3XtD8X55vePxjNfe3bk3H+nu+96uPf54POtnj8fe8OQX30JwOIxhtzYBh6MGOsPF0z9p2iWwpd8LjX6W5vWUYaiqlBuvLT5b5TQDPlRwmMSAABBg+kCer+XuAeQf4tL9tAxJ/hIfZGSm8rhyEfjytfxr9FeSX+KjvVfipNVpWlaPNhsAEPCS7Ao8FYnRlbO4ksLnjiSQvIanv4FNjwJ5pXIlMq6MQpIqqPnQKQKbjuPDtZlG55o6UHXWtVncZZTbbNBVB1P5dJYguCbJJ1WjOG8PVOioSm5HPrVt1rwuyN+K+PSZnNV1M/MmEFubfFjjU9tmK9XBJ2cOk3DDdZiEG67DJOrGuA7HyvAe12ESAxa73KPrN1z8gUikCCdvcD5NXnpQpA8nNhh9m5Yn4ZMrV8dHV/8a/dRA0x419a3lI9GBtM2GcrGYFXRNUU5TyluTOpdXwqeUt6YOpby9DUTLZylOcRlzdBTf2yV3ZBFOmKSHQh5KpjSSSpqG4s6VkUubqw8W8knTSnWk0Y+2jF5tlmuDUloJn6T8gRVcEpJ+3srChHSNt8RJsq4p+S41LC13KTcu/RJt1pLPKY1Pzhwm4YbrMAk3XIdJTMe4aeCjJhBVk0YiQ1MWZHhLgmO5QNVWfKRlavlIIQnurQmcnaMjSbBxhtMwYUxODpLcl2tUhvPlNE6VkiuoFVLXKT6ZfBjxRIIzOSlgWpLSB8uZ0g3BjeVDlFGEos0mfKKL7CQrY2ES7pM2i/OX22w4/sWReEhEnUOTxx3a+FrawQGZh04/rWe6oJBKo5zT4zLjPHE9ZHym5YzToogzfQcmfLgOhuLF/Sjm2izVDyXnrKtcmmmdaKumf+RyCw5Xn7OmzQaJF0fiEZG6BjXpYUYaSVkaPrXeHe4eVaZEr3Prqrmmrbc2T8lrmOMjn5xJHeJLYkk+PfzNTxOflrwF0EeHbU0Zt2wsW+PTkncB7g5zmMSwzUfS4eDhPa7DJK5jXGorsnZxonbRIbeAoOUjkUvlp+qxFp9YNuWL0nBqsVCkqUsrHQnuX+Nx5/qcJDI0kWgtJh7ihYCN8aG+13DqOXlbWUfD+fN0AUEmp3RcUWlVEwCynb5ssYLnxHViJT6ULCykb8EnzUfpqBWfVAdcnt5tprGhIe10WnjHpB2FtMPWcpM66yXyOad4Lz4Srq34SHhwZfRos1w9Y/jkzGESvj3dYRLe4zpMwg3XYRJuuA6T4M/Hzfk/OGd9OP2HOE2f8wtBlCebJrkfp+Gc3AGmiSiuaVlpwkmajL4osPUm9FMqIzBOJolfjGuzEtdUwWl53Dm7Eh9pzIdps+FiYJyi1N+Rvs/6OLCQBul8Ip8R08ik3EwhLZz1Wv8XmU7ZZqX7OT2gUIB2oaRBm+2ovDm5nM+ulEeiD8yka8UnJ1PCP82r9YWW8iCU5XO8W/PhPmvllNKW7lEyszsgNKuzkspJFZFL15uPtIweq7A1xiKpz1J8tGXP+dE53/fJmcMk6hcgJO8XqokEKi5uYzTG29LqSev95JqyKsoOOxjNpKQBD7VFc5GBJRsi+NQHkkv6+7m/UxTufwLCCy+CbAruyOLDdwEf/uf6vbbNJukzlogZC6wMdhAcM7ohHPawe/GrcO+HPwe4u782G7sIAE9++0vYv/YKwO6usfCaka0etgwXAGB3D8JznwIYnlmbiW0M92FbQy0d+MmZ3Xo5JDDcvuXJ2ZYqtyUuTwuM6nSXctcufHCOZqkjPScXhbIcdeD0XUpfKyNNy8nlyhuozLkM8XxR6pjm7tc4Fdx620I7lWq10JCm0ZanWoBwm3FsBe1WznpadbTg4A9PI2xx7FUKHopQjg7TKqNnpbioIUcFUGUsy1CS8fFYBYdJuOE6TMIN12ESgyiKiwO1bQOJe1w+6p42Etmhwmi6kLZXfC2G9IUj2vulY2wIPrv4onRhIXcRqS0DiWxkhF0uIb37wG22LRCSuVCyekC2GSXj9CG3YyT+krWh+KPAhkTvgGDKqbqnWbBwY+2Pnm3Wy4aMRYc1MuPDvp0skwgAh8PaJGbh5k4kx0f/hce/ewnw/QenXQCTFJDfQy45PzFNn5NHsoPy/u6gzE+nObzz91P9Z+6kWAm2zg6bDMoq8OQxHN78Axze/htAaB1EbQhhdzyfgRqIGoCxoUIjhDuA3ZDpcR0W4C3nMInbNVw7v4oOAsehArVFPL0uOjMM+DlM+pk7t7/BDuwcJsM6gcM7WweOX05nFCHNi12ASRfLo3QaX9O0GWTylOTnZIMwf4YPPTlD4iMm7aZwAGOUf3Rf48wjHNzVOMkKFA8pp0RHZ1mjdihs5R61PWbsWlphgs/E5gptNvFfSLY8QPk7dVbh+UNg8qfnJsZ8Bo0hzF0Y2Nqvc0s+Vbs5YL5OLfPRcorT2hvjtuxyHWZhzHCX6AMcFtB2B0RvtKZqqe6OEYz1uA7HEbdruN7ZmsZtGq4brXnQhlsbLFkDrY9mC9giH41/dSlONfeEIBcgss7nXopInPdkYN95J3XD1bMgkJUNFOxsDNLgyiynhYyX5dnAhnLyhzmO4V7IO8+xyZEgx5UqvJ41rOUTdhBOr2w6KjZc+B1FBkLGVUoAABQEcmPu6rPPw73v/gh2n/wMANYEhAd4/NqvYf/Wn5pEyPW2IUrOzQWSHyHdkEJgN8D97/0Edp/7GgDu9fnDDvD9t+HRqy8BPvxQ9i6xEXUEuPcMDF//Puw+/aVqDewfvA77f/zx9M40e7jNeNw5CDu4++K34e4r36kWcXj3TYDfvwz8D79ml1clDPuxx9FhuUik0rblVihFWLX+7ZFEXE2ioLBNg9fUSRopVsOjJbioskZlDuyAvmflpOWsOUNu/cBQ8jW/1A0np11RG+GjwG36cQHqFWnBcG4Axgx37d/I1uXXcvCnx6BXoQXf3mOAzvVpooJzaOcWdKBH1fZ07dCsFZpNgmfZbaOJ2dxnpwkNFC3C9MBcGxo0OugxwV8LWKm5lg9sFQdszKGhLAla2dCuduuOZcypx+UXdk0OK5e/hXKNTc4cjiPGhtvTX1njI6Z2+vbuKtaKspLooXdkXs1u5yUR7/LdROMsraSSIfTa6pqWodE9Mvla6sCI8d7uUMEXIEzjdg3XYRr2osOePIbDR+9BGO7re78QAD/+AODwpK5sBDg6dGyGAtL1sYnLGDe3+2BNTNycYQf7B2/Aw5d/XB9HejjA4YN3jgHUNQ132MOTv/wG9v98A+CgFBCO/+FH/wJ89PBaSY1OULZzQyQL2skayVwg/7Dk3Ky2IlcEgEcfw/7dt+YJnRP1f9jDoz+/AvM0FU4c1u8mes59e+ZXDhXmPE+tForD+lH73Q6EluiozfaldnzWQUWQzdprPk87lg44nkTKN+DT/10S7lW4VYz8wWucOTAPtl5e4mgfjmu0/b3HdZiEG67DJNxwbxlGhwkAuZeXAJS3Qpfemq7dds1tS5dsbc6dAyQpS5uGe+lKrJLSGUqlCb2GcwUuCxBzt71T2/g7t9mQniofv0yjWOtMYdSLM6Sy0pd5iLdFSQtUyiJtRnjmGOdhqq5bo5WzUXAYzns2Lu2tjaqb0WaTHRBrR9cvEVG4VF3WkLsGnzXqohzjbk3dt4hG/jDDxy8BLL5y5miBZi1wa9vT14dJ0o2qft6/1GhQZ1SV9uJxd3cQ7j+XD7RJ40JK38/XAPKz4ly+OG+KwOTDwn0uDSKEZ58/vgH+hmHLcA97uPvCN+G5H/wMoCaQ/KkAAtzdg/DCZ9cmsipsGS4ce5u7z38DYHhmbTL2YfjBH28DOM80s+MoxllVvfkwKudSbiL0dB0NTya2iGpNYmIzl+/EdexjQ8PEGE4FhdPHMAlbLhcsdWaPnfDEAxQJnbx53TEPJ51j3N7CrEfbSNt+arzXt57X2RBx94LsUGHOGRQtF7Fa8HFQQOabJmc5XQ8b8iAbh0mYNFzvdefD+nRhyPowqWitc2VbRyutGCF18+ilU2mEXWX51zFuKbqlZ/RLy0gixzagiS6sgL2hghuwAywarsMBxgzXO9u2sBzZWHwHRLwrQ5rWYQBIfuwCKnZJEpvEYSg9dRoncnejtdxFbBRLqFQzr5fSudH3nDmOaH26yHIwNcZ1NIZNmwWArYU1Fg8HDLB/7wH879VfAey2Rd0a9g/+2ubUyZUOdAz//umXjT136GPd2cDNnM9bC4Pd1gbOx3WsDh/jOkzCDddhEpcjmKiFhvGLQwDitJNrYTz05H7MS+N56hiq0mbYCfeIj2STb2s+cSJEOrguJ4fScaneOW7kOWZJm4VCmaPFg8wKgcSGuLpzR49Rerm8vIRaaECgvyB1Tbl9qOZoMiykHeVhVoZKwW9N+CSJuPwsH4YY12aTa5TxYyZPpsxSDG/Rhgp1lyxUnK/7UMFhEm64DpNIlnzTAdXcsJml8rdO1yt/K+R45EJUluS9zHaWITuQJb9rsVT+HvuKe+RvhdIIcE3ey4Rj+VDBYRJuuA6TcMN1mMT15SWMZ5h10Oc86+dr50s14QWch7rEh5PHef+psgsyqB0iI2e+hE+pDlpvvkQ/uVUMDfdSnTq12TA58injFUdOMPB5AeiALtHcUrstXrqSINnaoVjxyE5ra1ZipHMsTV2kMiQ8NDw7tdmqQ4WtzNEd9uBjXIdJuOE6TMLoy0sct46KHndNS6d2pW5tp+rW+Jw5rVl2qpP5Oqrcnr52w9RMgbfA8db5tAsp8DGuwyTaGW6DB7ppn9CCzxKnvKz9Kz7j/prUi0cwqQLQDBtvrp5uvMc/Wf00oFAT5FjscbcwMloCt1LPWvTUT41sH+M6TMIN12ESw3UPd8gPtrh7JeTyXvZGn0KD0jSlMms5Sfhw92vkUvXT5tPWt3WbSfjMsSFl3ujlJdy+4xkjnFze+PWrNWXWclqaT6t82vq2bjMJnzk2pMzrQwWHSbjhOkzCDdchxpZchpezwySQvHhiyVMLevPRctXwqeWmfcv5GaVTGKRy557YIHnhpETeoCl05grhbPlL89HK1vCp5darvZbgo+XEwYcKDpNww3WYxC6/U5PY5oun66MzPHH8L05PpqHKghn+TpjyictkZQLPh4u6yeknvXeWU+JD6TDHJ/cbn93Bi8nnDKdJm8EG2+zIZwBudlbjUOYOpj1frClPwyf3OZuXuaEx3lgWZixKxIfZ911rvJO65PRFVmZjbYY+VHDYhBuuwyTccB0mcdkB0cr5z70pW/pm7Bo+LesgqUsrPjVye9WXkqld8FiizRCi6LBWjmTRPGGG/JZ5ejvoa1ai1qwvlWarbeZDBYdJuOE6TKKP4W7xJdFb4+R8ZvH5P852gxhpwOZ9AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIwLTA4LTIzVDE0OjUyOjAwKzAyOjAwetRgVgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyMC0wOC0yM1QxNDo1MTo1OCswMjowMJuxI+oAAAAASUVORK5CYII= +# yamllint disable-line rule:line-length diff --git a/shuffle-tools-fork/1.2.0/docker-compose.yml b/shuffle-tools-fork/1.2.0/docker-compose.yml new file mode 100644 index 00000000..4919dcf4 --- /dev/null +++ b/shuffle-tools-fork/1.2.0/docker-compose.yml @@ -0,0 +1,15 @@ +version: '3.4' +services: + shuffle-tools-fork: + build: + context: . + dockerfile: Dockerfile +# image: walkoff_registry:5000/walkoff_app_HelloWorld-v1-0 + deploy: + mode: replicated + replicas: 10 + restart_policy: + condition: none + restart: "no" + secrets: + - secret1 diff --git a/shuffle-tools-fork/1.2.0/requirements.txt b/shuffle-tools-fork/1.2.0/requirements.txt new file mode 100644 index 00000000..67a560ec --- /dev/null +++ b/shuffle-tools-fork/1.2.0/requirements.txt @@ -0,0 +1,11 @@ +ioc_finder==7.2.1 +py7zr==0.11.3 +rarfile==4.0 +pyminizip==0.2.4 +requests==2.25.1 +xmltodict==0.11.0 +json2xml==5.0.5 +ipaddress==1.0.23 +google.auth==1.23.0 +paramiko==3.1.0 +shufflepy \ No newline at end of file diff --git a/shuffle-tools-fork/1.2.0/run.sh b/shuffle-tools-fork/1.2.0/run.sh new file mode 100644 index 00000000..84c016d6 --- /dev/null +++ b/shuffle-tools-fork/1.2.0/run.sh @@ -0,0 +1,11 @@ +# Build testing +NAME=frikky/shuffle:shuffle-tools-fork_1.1.0 +docker rmi $NAME --force +docker build . -t frikky/shuffle:shuffle-tools-fork_1.1.0 + +# Run testing +#docker run -e SHUFFLE_SWARM_CONFIG=run -e SHUFFLE_APP_EXPOSED_PORT=33334 frikky/shuffle:shuffle-tools_1.1.0 +echo $NAME +#docker service create --env SHUFFLE_SWARM_CONFIG=run --env SHUFFLE_APP_EXPOSED_PORT=33334 $NAME + +#cat walkoff_app_sdk/app_base.py #cat walkoff_app_sdk/app_sdk.py diff --git a/shuffle-tools-fork/1.2.0/src/app.py b/shuffle-tools-fork/1.2.0/src/app.py new file mode 100644 index 00000000..2f3cd991 --- /dev/null +++ b/shuffle-tools-fork/1.2.0/src/app.py @@ -0,0 +1,127 @@ +import hmac +import datetime +import json +import time +import markupsafe +import os +import re +import subprocess +import tempfile +import zipfile +import base64 +import ipaddress +import hashlib +import shufflepy +from io import StringIO +from contextlib import redirect_stdout +import random +import string + +import xmltodict +from json2xml import json2xml +from json2xml.utils import readfromstring + +from ioc_finder import find_iocs +from dateutil.parser import parse as dateutil_parser +from google.auth import crypt +from google.auth import jwt + +import py7zr +import pyminizip +import rarfile +import requests +import tarfile +import binascii +import struct + +import paramiko +import concurrent.futures +import multiprocessing + +from walkoff_app_sdk.app_base import AppBase + +class Tools(AppBase): + __version__ = "1.2.0" + app_name = ( + "Shuffle Tools Fork" # this needs to match "name" in api.yaml for WALKOFF to work + ) + + def __init__(self, redis, logger, console_logger=None): + """ + Each app should have this __init__ to set up Redis and logging. + :param redis: + :param logger: + :param console_logger: + """ + super().__init__(redis, logger, console_logger) + + def execute_python(self, code): + if len(code) == 36 and "-" in code: + filedata = self.get_file(code) + if filedata["success"] == False: + return { + "success": False, + "message": f"Failed to get file for ID {code}", + } + + if ".py" not in filedata["filename"]: + return { + "success": False, + "message": f"Filename needs to contain .py", + } + + + # Write the code to a file + # 1. Take the data into a file + # 2. Subprocess execute file? + try: + f = StringIO() + def custom_print(*args, **kwargs): + return print(*args, file=f, **kwargs) + + #with redirect_stdout(f): # just in case + # Add globals in it too + globals_copy = globals().copy() + globals_copy["print"] = custom_print + + # Add self to globals_copy + for key, value in locals().copy().items(): + if key not in globals_copy: + globals_copy[key] = value + + globals_copy["self"] = self + + exec(code, globals_copy) + + s = f.getvalue() + f.close() # why: https://www.youtube.com/watch?v=6SA6S9Ca5-U + + #try: + # s = s.encode("utf-8") + #except Exception as e: + + try: + return { + "success": True, + "message": json.loads(s.strip()), + } + except Exception as e: + try: + return { + "success": True, + "message": s.strip(), + } + except Exception as e: + return { + "success": True, + "message": s, + } + + except Exception as e: + return { + "success": False, + "message": f"exception: {e}", + } + +if __name__ == "__main__": + Tools.run() diff --git a/shuffle-tools-fork/1.2.0/src/concurrency.py b/shuffle-tools-fork/1.2.0/src/concurrency.py new file mode 100644 index 00000000..420d1686 --- /dev/null +++ b/shuffle-tools-fork/1.2.0/src/concurrency.py @@ -0,0 +1,201 @@ +import time +import json +import ipaddress +import concurrent.futures +from functools import partial +from ioc_finder import find_iocs + +class Test(): + def split_text(self, text): + # Split text into chunks of 10kb. Add each 10k to array + # In case e.g. 1.2.3.4 lands exactly on 20k boundary, it may be useful to overlap here. + # (just shitty code to reduce chance of issues) while still going fast + + arr_one = [] + max_len = 2500 + current_string = "" + overlaps = 100 + + + for i in range(0, len(text)): + current_string += text[i] + if len(current_string) > max_len: + # Appending just in case even with overlaps + if len(text) > i+overlaps: + current_string += text[i+1:i+overlaps] + else: + current_string += text[i+1:] + + arr_one.append(current_string) + current_string = "" + + if len(current_string) > 0: + arr_one.append(current_string) + + #print("DATA:", arr_one) + print("Strings:", len(arr_one)) + #exit() + + return arr_one + + def _format_result(self, result): + final_result = {} + + for res in result: + for key, val in res.items(): + if key in final_result: + if isinstance(val, list) and len(val) > 0: + for i in val: + final_result[key].append(i) + elif isinstance(val, dict): + #print(key,":::",val) + if key in final_result: + if isinstance(val, dict): + for k,v in val.items(): + #print("k:",k,"v:",v) + val[k].append(v) + #print(val) + #final_result[key].append([i for i in val if len(val) > 0]) + else: + final_result[key] = val + + return final_result + + def worker_function(self, inputdata): + return find_iocs(inputdata["data"], included_ioc_types=inputdata["ioc_types"]) + + def _with_concurency(self, array_of_strings, ioc_types): + results = [] + #start = time.perf_counter() + + # Workers dont matter..? + # What can we use instead? + + results = [] + workers = 4 + with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as executor: + # Submit the find_iocs function for each string in the array + futures = [executor.submit( + find_iocs, + text=string, + included_ioc_types=ioc_types, + ) for string in array_of_strings] + + # Wait for all tasks to complete + concurrent.futures.wait(futures) + + # Retrieve the results if needed + results = [future.result() for future in futures] + + return self._format_result(results) + + def parse_ioc_new(self, input_string, input_type="all"): + if input_type == "": + input_type = "all" + + #ioc_types = ["domains", "urls", "email_addresses", "ipv6s", "ipv4s", "ipv4_cidrs", "md5s", "sha256s", "sha1s", "cves"] + ioc_types = ["domains", "urls", "email_addresses", "ipv4s", "ipv4_cidrs", "md5s", "sha256s", "sha1s", "cves"] + + # urls = 10.4 -> 9.1 + # emails = 10.4 -> 9.48 + # ipv6s = 10.4 -> 7.37 + # ipv4 cidrs = 10.4 -> 10.44 + + if input_type == "" or input_type == "all": + ioc_types = ioc_types + else: + input_type = input_type.split(",") + for item in input_type: + item = item.strip() + + ioc_types = input_type + + input_string = str(input_string) + if len(input_string) > 10000: + iocs = self._with_concurency(self.split_text(input_string), ioc_types=ioc_types) + else: + iocs = find_iocs(input_string, included_ioc_types=ioc_types) + + newarray = [] + for key, value in iocs.items(): + if input_type != "all": + if key not in input_type: + continue + + if len(value) == 0: + continue + + for item in value: + # If in here: attack techniques. Shouldn't be 3 levels so no + # recursion necessary + if isinstance(value, dict): + for subkey, subvalue in value.items(): + if len(subvalue) == 0: + continue + + for subitem in subvalue: + data = { + "data": subitem, + "data_type": "%s_%s" % (key[:-1], subkey), + } + + if data not in newarray: + newarray.append(data) + else: + data = {"data": item, "data_type": key[:-1]} + if data not in newarray: + newarray.append(data) + + # Reformatting IP + i = -1 + for item in newarray: + i += 1 + if "ip" not in item["data_type"]: + continue + + newarray[i]["data_type"] = "ip" + try: + newarray[i]["is_private_ip"] = ipaddress.ip_address(item["data"]).is_private + except Exception as e: + print("Error parsing %s: %s" % (item["data"], e)) + + try: + newarray = json.dumps(newarray) + except json.decoder.JSONDecodeError as e: + return "Failed to parse IOC's: %s" % e + + return newarray + +# Make it not run this for multithreads +if __name__ == "__main__": + + input_string = "" + with open("testdata.txt", "r") as f: + input_string = f.read() + + try: + json_data = json.loads(input_string) + # If array, loop + if isinstance(json_data, list): + cnt = 0 + start = time.perf_counter() + for item in json_data: + cnt += 1 + classdata = Test() + + ret = classdata.parse_ioc_new(item) + #print("OUTPUT1: ", ret) + + #if cnt == 5: + # break + + print("Total time taken:", time.perf_counter()-start) + else: + classdata = Test() + ret = classdata.parse_ioc_new(input_string) + print("OUTPUT2: ", ret) + except Exception as e: + classdata = Test() + ret = classdata.parse_ioc_new(json_data) + print("OUTPUT3: ", ret) + diff --git a/shuffle-tools-fork/1.2.0/src/switch.py b/shuffle-tools-fork/1.2.0/src/switch.py new file mode 100644 index 00000000..78ede505 --- /dev/null +++ b/shuffle-tools-fork/1.2.0/src/switch.py @@ -0,0 +1,203 @@ +# self, sourcevalue, condition, destinationvalue +def validate_condition(sourcevalue, check, destinationvalue): + if check == "=" or check == "==" or check.lower() == "equals": + if str(sourcevalue).lower() == str(destinationvalue).lower(): + return True + elif check == "!=" or check.lower() == "does not equal": + if str(sourcevalue).lower() != str(destinationvalue).lower(): + return True + elif check.lower() == "startswith": + if str(sourcevalue).lower().startswith(str(destinationvalue).lower()): + return True + + + elif check.lower() == "endswith": + if str(sourcevalue).lower().endswith(str(destinationvalue).lower()): + return True + elif check.lower() == "contains": + if destinationvalue.lower() in sourcevalue.lower(): + return True + + elif check.lower() == "is empty" or check.lower() == "is_empty": + try: + if len(json.loads(sourcevalue)) == 0: + return True + except Exception as e: + print("[ERROR] Failed to check if empty as list: {e}") + + if len(str(sourcevalue)) == 0: + return True + + elif check.lower() == "contains_any_of": + newvalue = [destinationvalue.lower()] + if "," in destinationvalue: + newvalue = destinationvalue.split(",") + elif ", " in destinationvalue: + newvalue = destinationvalue.split(", ") + + for item in newvalue: + if not item: + continue + + if item.strip() in sourcevalue: + return True + + elif check.lower() == "larger than" or check.lower() == "bigger than" or check == ">" or check == ">=": + try: + if str(sourcevalue).isdigit() and str(destinationvalue).isdigit(): + if int(sourcevalue) > int(destinationvalue): + return True + + except AttributeError as e: + print("[WARNING] Condition larger than failed with values %s and %s: %s" % (sourcevalue, destinationvalue, e)) + + try: + destinationvalue = len(json.loads(destinationvalue)) + except Exception as e: + print("[WARNING] Failed to convert destination to list: {e}") + try: + # Check if it's a list in autocast and if so, check the length + if len(json.loads(sourcevalue)) > int(destinationvalue): + return True + except Exception as e: + print("[WARNING] Failed to check if larger than as list: {e}") + + + elif check.lower() == "smaller than" or check.lower() == "less than" or check == "<" or check == "<=": + print("In smaller than check: %s %s" % (sourcevalue, destinationvalue)) + + try: + if str(sourcevalue).isdigit() and str(destinationvalue).isdigit(): + if int(sourcevalue) < int(destinationvalue): + return True + + except AttributeError as e: + pass + + try: + destinationvalue = len(json.loads(destinationvalue)) + except Exception as e: + print("[WARNING] Failed to convert destination to list: {e}") + + try: + # Check if it's a list in autocast and if so, check the length + if len(json.loads(sourcevalue)) < int(destinationvalue): + return True + except Exception as e: + print("[WARNING] Failed to check if smaller than as list: {e}") + + elif check.lower() == "re" or check.lower() == "matches regex": + try: + found = re.search(str(destinationvalue), str(sourcevalue)) + except re.error as e: + return False + except Exception as e: + return False + + if found == None: + return False + + return True + else: + print("[DEBUG] Condition: can't handle %s yet. Setting to true" % check) + + return False + +def evaluate_conditions(condition_structure): + operator = condition_structure.get('operator') + + # Base case: Single condition + if 'source' in condition_structure: + source = condition_structure['source'] + condition = condition_structure['condition'] + destination = condition_structure['destination'] + + # self. + return validate_condition(source, condition, destination) + + # Recursive case: Logical operator + elif operator == "AND": + return all(evaluate_conditions(sub_condition) for sub_condition in condition_structure['conditions']) + + elif operator == "OR": + return any(evaluate_conditions(sub_condition) for sub_condition in condition_structure['conditions']) + + elif operator == "NOT": + return not evaluate_conditions(condition_structure['conditions'][0]) + + else: + raise ValueError(f"Unknown operator: {operator}") + + +def switch(conditions): + to_return = { + "success": True, + "run_else": True, + } + + for condition in conditions: + if "id" not in condition: + print("Condition ID not found") + continue + + evaluated = False + try: + evaluated = evaluate_conditions(condition) + except Exception as e: + print(f"Failed to evaluate condition {condition['id']}: {e}") + + if evaluated == True: + to_return["run_else"] = False + + to_return[condition["id"]] = evaluated + + return to_return + +# Example usage + +condition_structure = { + "id": "lol", + "operator": "AND", + "conditions": [ + { # true + "source": "20", # age + "condition": ">", + "destination": 18 + }, + { # true + "operator": "OR", + "conditions": [ + { + "source": "active", # status + "condition": "==", + "destination": "active" + }, + { + "source": "1500", # balance + "condition": ">=", + "destination": 1000 + } + ] + }, + { + "operator": "NOT", + "conditions": [ + { + "source": "user", # user + "condition": "==", + "destination": "admin" + } + ] + } + ] +} + +newcondition = condition_structure.copy() +testconditions = [condition_structure] +newcondition['id'] = "lol2" +testconditions.append(newcondition) + +result = switch(testconditions) +print() +print() +print("Output: ", result) From 593990bd3d4fd4d3e3e2472524d6744cd3e07ab4 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Fri, 29 Nov 2024 17:57:06 +0530 Subject: [PATCH 145/259] feat[shuffle-tools-fork]: Correct versioning + waiting for release --- shuffle-tools-fork/{1.2.0 => 1.0.0}/Dockerfile | 0 shuffle-tools-fork/{1.2.0 => 1.0.0}/api.yaml | 2 +- shuffle-tools-fork/{1.2.0 => 1.0.0}/docker-compose.yml | 0 shuffle-tools-fork/{1.2.0 => 1.0.0}/requirements.txt | 0 shuffle-tools-fork/{1.2.0 => 1.0.0}/run.sh | 4 ++-- shuffle-tools-fork/{1.2.0 => 1.0.0}/src/app.py | 0 shuffle-tools-fork/{1.2.0 => 1.0.0}/src/concurrency.py | 0 shuffle-tools-fork/{1.2.0 => 1.0.0}/src/switch.py | 0 8 files changed, 3 insertions(+), 3 deletions(-) rename shuffle-tools-fork/{1.2.0 => 1.0.0}/Dockerfile (100%) rename shuffle-tools-fork/{1.2.0 => 1.0.0}/api.yaml (99%) rename shuffle-tools-fork/{1.2.0 => 1.0.0}/docker-compose.yml (100%) rename shuffle-tools-fork/{1.2.0 => 1.0.0}/requirements.txt (100%) rename shuffle-tools-fork/{1.2.0 => 1.0.0}/run.sh (76%) rename shuffle-tools-fork/{1.2.0 => 1.0.0}/src/app.py (100%) rename shuffle-tools-fork/{1.2.0 => 1.0.0}/src/concurrency.py (100%) rename shuffle-tools-fork/{1.2.0 => 1.0.0}/src/switch.py (100%) diff --git a/shuffle-tools-fork/1.2.0/Dockerfile b/shuffle-tools-fork/1.0.0/Dockerfile similarity index 100% rename from shuffle-tools-fork/1.2.0/Dockerfile rename to shuffle-tools-fork/1.0.0/Dockerfile diff --git a/shuffle-tools-fork/1.2.0/api.yaml b/shuffle-tools-fork/1.0.0/api.yaml similarity index 99% rename from shuffle-tools-fork/1.2.0/api.yaml rename to shuffle-tools-fork/1.0.0/api.yaml index cc04349f..a97d1dc2 100644 --- a/shuffle-tools-fork/1.2.0/api.yaml +++ b/shuffle-tools-fork/1.0.0/api.yaml @@ -1,5 +1,5 @@ --- -app_version: 1.2.0 +app_version: 1.0.0 name: Shuffle Tools Fork description: A tool app for Shuffle. Gives access to most missing features along with Liquid. tags: diff --git a/shuffle-tools-fork/1.2.0/docker-compose.yml b/shuffle-tools-fork/1.0.0/docker-compose.yml similarity index 100% rename from shuffle-tools-fork/1.2.0/docker-compose.yml rename to shuffle-tools-fork/1.0.0/docker-compose.yml diff --git a/shuffle-tools-fork/1.2.0/requirements.txt b/shuffle-tools-fork/1.0.0/requirements.txt similarity index 100% rename from shuffle-tools-fork/1.2.0/requirements.txt rename to shuffle-tools-fork/1.0.0/requirements.txt diff --git a/shuffle-tools-fork/1.2.0/run.sh b/shuffle-tools-fork/1.0.0/run.sh similarity index 76% rename from shuffle-tools-fork/1.2.0/run.sh rename to shuffle-tools-fork/1.0.0/run.sh index 84c016d6..bd26dbf6 100644 --- a/shuffle-tools-fork/1.2.0/run.sh +++ b/shuffle-tools-fork/1.0.0/run.sh @@ -1,7 +1,7 @@ # Build testing -NAME=frikky/shuffle:shuffle-tools-fork_1.1.0 +NAME=frikky/shuffle:shuffle-tools-fork_1.0.0 docker rmi $NAME --force -docker build . -t frikky/shuffle:shuffle-tools-fork_1.1.0 +docker build . -t frikky/shuffle:shuffle-tools-fork_1.0.0 # Run testing #docker run -e SHUFFLE_SWARM_CONFIG=run -e SHUFFLE_APP_EXPOSED_PORT=33334 frikky/shuffle:shuffle-tools_1.1.0 diff --git a/shuffle-tools-fork/1.2.0/src/app.py b/shuffle-tools-fork/1.0.0/src/app.py similarity index 100% rename from shuffle-tools-fork/1.2.0/src/app.py rename to shuffle-tools-fork/1.0.0/src/app.py diff --git a/shuffle-tools-fork/1.2.0/src/concurrency.py b/shuffle-tools-fork/1.0.0/src/concurrency.py similarity index 100% rename from shuffle-tools-fork/1.2.0/src/concurrency.py rename to shuffle-tools-fork/1.0.0/src/concurrency.py diff --git a/shuffle-tools-fork/1.2.0/src/switch.py b/shuffle-tools-fork/1.0.0/src/switch.py similarity index 100% rename from shuffle-tools-fork/1.2.0/src/switch.py rename to shuffle-tools-fork/1.0.0/src/switch.py From 6004fde77ae4866b7463e4dbce8c731a801eeaad Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Fri, 29 Nov 2024 18:41:40 +0530 Subject: [PATCH 146/259] feat[shuffle-tools-fork]: Adding an option to install python apps --- shuffle-tools-fork/1.0.0/src/app.py | 55 ++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/shuffle-tools-fork/1.0.0/src/app.py b/shuffle-tools-fork/1.0.0/src/app.py index 2f3cd991..23e5b522 100644 --- a/shuffle-tools-fork/1.0.0/src/app.py +++ b/shuffle-tools-fork/1.0.0/src/app.py @@ -38,6 +38,9 @@ import concurrent.futures import multiprocessing +from pip._internal import main as pip_main +from pip._internal.commands.show import search_packages_info + from walkoff_app_sdk.app_base import AppBase class Tools(AppBase): @@ -54,8 +57,58 @@ def __init__(self, redis, logger, console_logger=None): :param console_logger: """ super().__init__(redis, logger, console_logger) + + def get_missing_packages(required_packages: list) -> list: + """ + Returns a list of packages that aren't currently installed. + + Args: + required_packages: List of package names (can include version specs) + + Returns: + List of package names that aren't installed + """ + missing = [] + for package in required_packages: + # Remove version specifiers if present (e.g., 'pandas>=1.0.0' -> 'pandas') + package_name = package.split('==')[0].split('>=')[0].split('<=')[0].split('>')[0].split('<')[0].strip() + + # Check if package exists in environment + if not list(search_packages_info([package_name])): + missing.append(package) + + return missing + + def install_packages(self, packages=[]) -> None: + """ + Install Python packages using pip's Python interface. + + Args: + packages: List of package names to install + """ + + packages_not_found = self.get_missing_packages(packages) + + for package in packages_not_found: + try: + pip_main(['install', package]) + print(f"Successfully installed {package}") + except Exception as e: + print(f"Failed to install {package}: {str(e)}") - def execute_python(self, code): + def execute_python(self, code, packages=[]) -> dict: + if os.getenv("ALLOW_PACKAGE_INSTALL") == "true": + allow_package_install = True + + if packages: + if allow_package_install: + self.install_packages(packages) + else: + return { + "success": False, + "message": "Package installation is disabled in this environment", + } + if len(code) == 36 and "-" in code: filedata = self.get_file(code) if filedata["success"] == False: From bb43d093c60f30f4ae1cb12fc50e0572db4c21b0 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Fri, 29 Nov 2024 18:45:53 +0530 Subject: [PATCH 147/259] fix[shuffle-tools-fork]: using correct variable standards --- shuffle-tools-fork/1.0.0/src/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shuffle-tools-fork/1.0.0/src/app.py b/shuffle-tools-fork/1.0.0/src/app.py index 23e5b522..9bb67ff9 100644 --- a/shuffle-tools-fork/1.0.0/src/app.py +++ b/shuffle-tools-fork/1.0.0/src/app.py @@ -97,7 +97,7 @@ def install_packages(self, packages=[]) -> None: print(f"Failed to install {package}: {str(e)}") def execute_python(self, code, packages=[]) -> dict: - if os.getenv("ALLOW_PACKAGE_INSTALL") == "true": + if os.getenv("SHUFFLE_ALLOW_PACKAGE_INSTALL") == "true": allow_package_install = True if packages: From 1684e68f9fa8f7b24227059fa0269eec51fbf15e Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Fri, 29 Nov 2024 18:52:38 +0530 Subject: [PATCH 148/259] fix[shuffle-tools-fork]: Making dynamic imports possible --- shuffle-tools-fork/1.0.0/src/app.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/shuffle-tools-fork/1.0.0/src/app.py b/shuffle-tools-fork/1.0.0/src/app.py index 9bb67ff9..95e8af98 100644 --- a/shuffle-tools-fork/1.0.0/src/app.py +++ b/shuffle-tools-fork/1.0.0/src/app.py @@ -9,6 +9,7 @@ import tempfile import zipfile import base64 +import importlib import ipaddress import hashlib import shufflepy @@ -58,6 +59,11 @@ def __init__(self, redis, logger, console_logger=None): """ super().__init__(redis, logger, console_logger) + def dynamic_import(package_name: str): + """Import a package and return the module""" + return importlib.import_module(package_name.split('==')[0].split('>=')[0].split('<=')[0].split('>')[0].split('<')[0]) + + def get_missing_packages(required_packages: list) -> list: """ Returns a list of packages that aren't currently installed. @@ -103,11 +109,8 @@ def execute_python(self, code, packages=[]) -> dict: if packages: if allow_package_install: self.install_packages(packages) - else: - return { - "success": False, - "message": "Package installation is disabled in this environment", - } + self.dynamic_import(packages) + if len(code) == 36 and "-" in code: filedata = self.get_file(code) From 52568965556fb8193e485262ea87a1e8fc59c9c7 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Fri, 29 Nov 2024 19:59:37 +0530 Subject: [PATCH 149/259] fix[shuffle-tools-fork]: Making dynamic imports possible --- shuffle-tools-fork/1.0.0/api.yaml | 7 +++++++ shuffle-tools-fork/1.0.0/src/app.py | 7 ++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/shuffle-tools-fork/1.0.0/api.yaml b/shuffle-tools-fork/1.0.0/api.yaml index a97d1dc2..3f3d116a 100644 --- a/shuffle-tools-fork/1.0.0/api.yaml +++ b/shuffle-tools-fork/1.0.0/api.yaml @@ -22,6 +22,13 @@ actions: example: print("hello world") schema: type: string + - name: packages + description: The code to run. Can be a file ID from within Shuffle. + required: true + multiline: true + example: pandas\nnumpy\nmatplotlib + schema: + type: string large_image: data:image/png;base64,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 # yamllint disable-line rule:line-length diff --git a/shuffle-tools-fork/1.0.0/src/app.py b/shuffle-tools-fork/1.0.0/src/app.py index 95e8af98..5d7b0038 100644 --- a/shuffle-tools-fork/1.0.0/src/app.py +++ b/shuffle-tools-fork/1.0.0/src/app.py @@ -102,16 +102,17 @@ def install_packages(self, packages=[]) -> None: except Exception as e: print(f"Failed to install {package}: {str(e)}") - def execute_python(self, code, packages=[]) -> dict: + def execute_python(self, code, packages) -> dict: if os.getenv("SHUFFLE_ALLOW_PACKAGE_INSTALL") == "true": allow_package_install = True + packages = packages.split("\n") if packages else [] + if packages: if allow_package_install: self.install_packages(packages) self.dynamic_import(packages) - - + if len(code) == 36 and "-" in code: filedata = self.get_file(code) if filedata["success"] == False: From e88bd573aad360771f93bbb091ffbc09c8c43ecf Mon Sep 17 00:00:00 2001 From: yashsinghcodes Date: Tue, 24 Dec 2024 01:59:07 +0530 Subject: [PATCH 150/259] improvement over filter_list --- shuffle-tools/1.2.0/src/app.py | 131 ++++++++++++++++++++++++++------- 1 file changed, 105 insertions(+), 26 deletions(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 593433a5..175abcd8 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -9,6 +9,7 @@ import tempfile import zipfile import base64 +import gzip import ipaddress import hashlib from io import StringIO @@ -53,6 +54,8 @@ def __init__(self, redis, logger, console_logger=None): :param logger: :param console_logger: """ + self.cache_update_buffer = [] + self.shared_cache = {} super().__init__(redis, logger, console_logger) def router(self): @@ -661,6 +664,62 @@ def check_wildcard(self, wildcardstring, matching_string): return False + def preload_cache(self, key): + org_id = self.full_execution["workflow"]["execution_org"]["id"] + url = f"{self.url}/api/v1/orgs/{org_id}/get_cache" + data = { + "workflow_id": self.full_execution["workflow"]["id"], + "execution_id": self.current_execution_id, + "authorization": self.authorization, + "org_id": org_id, + "key": key, + } + get_response = requests.post(url, json=data, verify=False) + response_data = get_response.json() + if "value" in response_data: + raw_value = response_data["value"] + if isinstance(raw_value, str): + try: + parsed = json.loads(raw_value) + except json.JSONDecodeError: + parsed = [raw_value] + else: + parsed = raw_value + + if not isinstance(parsed, list): + parsed = [parsed] + + response_data["value"] = parsed + return get_response.json() + + def check_compression(self, obj, threshold=1_000_000): + data_btyes = json.dumps(obj).encode("utf-8") + if len(data_btyes) > threshold: + return True + return False + + def compress_data(self, obj): + data_btyes = json.dumps(obj).encode("utf-8") + compressed_data = gzip.compress(data_btyes) + return base64.b64encode(compressed_data).decode("utf-8") + + def update_cache(self, key): + org_id = self.full_execution["workflow"]["execution_org"]["id"] + url = f"{self.url}/api/v1/orgs/{org_id}/set_cache" + data = { + "workflow_id": self.full_execution["workflow"]["id"], + "execution_id": self.current_execution_id, + "authorization": self.authorization, + "org_id": org_id, + "key": key, + "value": json.dumps(self.shared_cache["value"]), + } + + get_response = requests.post(url, json=data, verify=False) + self.cache_update_buffer = [] + return get_response.json() + + def filter_list(self, input_list, field, check, value, opposite): # Remove hashtags on the fly @@ -876,12 +935,20 @@ def filter_list(self, input_list, field, check, value, opposite): failed_list.append(item) elif check == "in cache key": + if item == input_list[0]: + self.shared_cache = self.preload_cache(key=value) + ret = self.check_cache_contains(value, tmp, "true") + if ret["success"] == True and ret["found"] == True: new_list.append(item) else: failed_list.append(item) + if len(self.cache_update_buffer) > 400 or (item == input_list[-1] and len(self.cache_update_buffer) > 0): + self.update_cache(value) + + #return { # "success": True, # "found": False, @@ -931,13 +998,16 @@ def filter_list(self, input_list, field, check, value, opposite): failed_list = tmplist try: - return json.dumps( - { + data ={ "success": True, "valid": new_list, "invalid": failed_list, } - ) + if self.check_compression(data): + data = self.compress_data(data) + return data + + return json.dumps(data) # new_list = json.dumps(new_list) except json.decoder.JSONDecodeError as e: return json.dumps( @@ -1737,7 +1807,6 @@ def escape_html(self, input_data): def check_cache_contains(self, key, value, append): org_id = self.full_execution["workflow"]["execution_org"]["id"] - url = "%s/api/v1/orgs/%s/get_cache" % (self.url, org_id) data = { "workflow_id": self.full_execution["workflow"]["id"], "execution_id": self.current_execution_id, @@ -1766,7 +1835,7 @@ def check_cache_contains(self, key, value, append): value = json.dumps(value) except Exception as e: pass - + if not isinstance(value, str): value = str(value) @@ -1778,11 +1847,13 @@ def check_cache_contains(self, key, value, append): append = False if "success" not in allvalues: - get_response = requests.post(url, json=data, verify=False) + #get_response = requests.post(url, json=data, verify=False) + pass try: if "success" not in allvalues: - allvalues = get_response.json() + #allvalues = get_response.json() + allvalues = self.shared_cache try: if allvalues["value"] == None or allvalues["value"] == "null": @@ -1799,6 +1870,7 @@ def check_cache_contains(self, key, value, append): set_response = requests.post(set_url, json=data, verify=False) try: allvalues = set_response.json() + self.shared_cache = self.preload_cache(key=key) #allvalues["key"] = key #return allvalues @@ -1830,19 +1902,26 @@ def check_cache_contains(self, key, value, append): if allvalues["value"] == None or allvalues["value"] == "null": allvalues["value"] = "[]" - allvalues["value"] = str(allvalues["value"]) + if isinstance(allvalues["value"], str): + try: + allvalues["value"] = json.loads(allvalues["value"]) + except json.JSONDecodeError: + self.logger.info("[WARNING] Failed inner value cache parsing") + allvalues["value"] = [allvalues["value"]] + + if not isinstance(allvalues["value"], list): + allvalues["value"] = [allvalues["value"]] try: - parsedvalue = json.loads(allvalues["value"]) + parsedvalue = json.loads(str(allvalues["value"])) except json.decoder.JSONDecodeError as e: - parsedvalue = [str(allvalues["value"])] - except Exception as e: - parsedvalue = [str(allvalues["value"])] + parsedvalue = allvalues["value"] try: for item in parsedvalue: #return "%s %s" % (item, value) - if item == value: + self.logger.info(f"{item} == {value}") + if str(item) == str(value): if not append: try: newdata = json.loads(json.dumps(data)) @@ -1858,7 +1937,7 @@ def check_cache_contains(self, key, value, append): "reason": "Found and not appending!", "key": key, "search": value, - "value": json.loads(allvalues["value"]), + "value": allvalues["value"], } else: return { @@ -1867,10 +1946,10 @@ def check_cache_contains(self, key, value, append): "reason": "Found, was appending, but item already exists", "key": key, "search": value, - "value": json.loads(allvalues["value"]), + "value": allvalues["value"], } - - # Lol + + # Lol break except Exception as e: parsedvalue = [str(parsedvalue)] @@ -1886,18 +1965,18 @@ def check_cache_contains(self, key, value, append): "value": json.loads(allvalues["value"]), } - new_value = parsedvalue - if new_value == None: - new_value = [value] + #parsedvalue.append(value) - new_value.append(value) - data["value"] = json.dumps(new_value) + #data["value"] = json.dumps(parsedvalue) - set_url = "%s/api/v1/orgs/%s/set_cache" % (self.url, org_id) - response = requests.post(set_url, json=data, verify=False) + if value not in allvalues["value"] and isinstance(allvalues["value"], list): + self.cache_update_buffer.append(value) + allvalues["value"].append(value) + #set_url = "%s/api/v1/orgs/%s/set_cache" % (self.url, org_id) + #response = requests.post(set_url, json=data, verify=False) exception = "" try: - allvalues = response.json() + #allvalues = response.json() #return allvalues return { @@ -1906,7 +1985,7 @@ def check_cache_contains(self, key, value, append): "reason": "Appended as it didn't exist", "key": key, "search": value, - "value": new_value, + "value": parsedvalue, } except Exception as e: exception = e From e98c46c28a1dfe28ac4804cddaf4bac807c7fe7a Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 6 Jan 2025 12:57:02 +0100 Subject: [PATCH 151/259] Minor fix --- shuffle-tools/1.2.0/src/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 593433a5..d7587f9e 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -1504,7 +1504,7 @@ def merge_lists(self, list_one, list_two, set_field="", sort_key_list_one="", so return {"success": False, "message": "Both input lists need to be valid JSON lists."} if len(list_one) != len(list_two): - return {"success": False, "message": "Lists length must be the same. %d vs %d" % (len(list_one), len(list_two))} + return {"success": False, "message": "Lists length must be the same. %d vs %d. Are you trying to add them to a single list? Use add_list_to_list" % (len(list_one), len(list_two))} if len(sort_key_list_one) > 0: try: From f541bf91b0d9b2782fae71e414e5a2807f9a2b51 Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 6 Jan 2025 12:57:39 +0100 Subject: [PATCH 152/259] Delete shuffle-tools-fork directory --- shuffle-tools-fork/1.0.0/Dockerfile | 27 --- shuffle-tools-fork/1.0.0/api.yaml | 34 ---- shuffle-tools-fork/1.0.0/docker-compose.yml | 15 -- shuffle-tools-fork/1.0.0/requirements.txt | 11 -- shuffle-tools-fork/1.0.0/run.sh | 11 -- shuffle-tools-fork/1.0.0/src/app.py | 184 ------------------ shuffle-tools-fork/1.0.0/src/concurrency.py | 201 ------------------- shuffle-tools-fork/1.0.0/src/switch.py | 203 -------------------- 8 files changed, 686 deletions(-) delete mode 100644 shuffle-tools-fork/1.0.0/Dockerfile delete mode 100644 shuffle-tools-fork/1.0.0/api.yaml delete mode 100644 shuffle-tools-fork/1.0.0/docker-compose.yml delete mode 100644 shuffle-tools-fork/1.0.0/requirements.txt delete mode 100644 shuffle-tools-fork/1.0.0/run.sh delete mode 100644 shuffle-tools-fork/1.0.0/src/app.py delete mode 100644 shuffle-tools-fork/1.0.0/src/concurrency.py delete mode 100644 shuffle-tools-fork/1.0.0/src/switch.py diff --git a/shuffle-tools-fork/1.0.0/Dockerfile b/shuffle-tools-fork/1.0.0/Dockerfile deleted file mode 100644 index 5c1a8af4..00000000 --- a/shuffle-tools-fork/1.0.0/Dockerfile +++ /dev/null @@ -1,27 +0,0 @@ -# Base our app image off of the WALKOFF App SDK image -FROM frikky/shuffle:app_sdk as base - -# We're going to stage away all of the bloat from the build tools so lets create a builder stage -FROM base as builder - -# Install all alpine build tools needed for our pip installs -RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev git - -# Install all of our pip packages in a single directory that we can copy to our base image later -RUN mkdir /install -WORKDIR /install -COPY requirements.txt /requirements.txt -RUN pip install --no-cache-dir --prefix="/install" -r /requirements.txt - -# Switch back to our base image and copy in all of our built packages and source code -FROM base -COPY --from=builder /install /usr/local -COPY src /app - -# Install any binary dependencies needed in our final image -# RUN apk --no-cache add --update my_binary_dependency -RUN apk --no-cache add jq git curl - -# Finally, lets run our app! -WORKDIR /app -CMD ["python", "app.py", "--log-level", "DEBUG"] diff --git a/shuffle-tools-fork/1.0.0/api.yaml b/shuffle-tools-fork/1.0.0/api.yaml deleted file mode 100644 index 3f3d116a..00000000 --- a/shuffle-tools-fork/1.0.0/api.yaml +++ /dev/null @@ -1,34 +0,0 @@ ---- -app_version: 1.0.0 -name: Shuffle Tools Fork -description: A tool app for Shuffle. Gives access to most missing features along with Liquid. -tags: - - Testing - - Shuffle -categories: - - Other -contact_info: - name: "@frikkylikeme" - url: https://shuffler.io - email: frikky@shuffler.io -actions: - - name: execute_python - description: Runs python with the data input. Any prints will be returned. - parameters: - - name: code - description: The code to run. Can be a file ID from within Shuffle. - required: true - multiline: true - example: print("hello world") - schema: - type: string - - name: packages - description: The code to run. Can be a file ID from within Shuffle. - required: true - multiline: true - example: pandas\nnumpy\nmatplotlib - schema: - type: string - -large_image: data:image/png;base64,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 -# yamllint disable-line rule:line-length diff --git a/shuffle-tools-fork/1.0.0/docker-compose.yml b/shuffle-tools-fork/1.0.0/docker-compose.yml deleted file mode 100644 index 4919dcf4..00000000 --- a/shuffle-tools-fork/1.0.0/docker-compose.yml +++ /dev/null @@ -1,15 +0,0 @@ -version: '3.4' -services: - shuffle-tools-fork: - build: - context: . - dockerfile: Dockerfile -# image: walkoff_registry:5000/walkoff_app_HelloWorld-v1-0 - deploy: - mode: replicated - replicas: 10 - restart_policy: - condition: none - restart: "no" - secrets: - - secret1 diff --git a/shuffle-tools-fork/1.0.0/requirements.txt b/shuffle-tools-fork/1.0.0/requirements.txt deleted file mode 100644 index 67a560ec..00000000 --- a/shuffle-tools-fork/1.0.0/requirements.txt +++ /dev/null @@ -1,11 +0,0 @@ -ioc_finder==7.2.1 -py7zr==0.11.3 -rarfile==4.0 -pyminizip==0.2.4 -requests==2.25.1 -xmltodict==0.11.0 -json2xml==5.0.5 -ipaddress==1.0.23 -google.auth==1.23.0 -paramiko==3.1.0 -shufflepy \ No newline at end of file diff --git a/shuffle-tools-fork/1.0.0/run.sh b/shuffle-tools-fork/1.0.0/run.sh deleted file mode 100644 index bd26dbf6..00000000 --- a/shuffle-tools-fork/1.0.0/run.sh +++ /dev/null @@ -1,11 +0,0 @@ -# Build testing -NAME=frikky/shuffle:shuffle-tools-fork_1.0.0 -docker rmi $NAME --force -docker build . -t frikky/shuffle:shuffle-tools-fork_1.0.0 - -# Run testing -#docker run -e SHUFFLE_SWARM_CONFIG=run -e SHUFFLE_APP_EXPOSED_PORT=33334 frikky/shuffle:shuffle-tools_1.1.0 -echo $NAME -#docker service create --env SHUFFLE_SWARM_CONFIG=run --env SHUFFLE_APP_EXPOSED_PORT=33334 $NAME - -#cat walkoff_app_sdk/app_base.py #cat walkoff_app_sdk/app_sdk.py diff --git a/shuffle-tools-fork/1.0.0/src/app.py b/shuffle-tools-fork/1.0.0/src/app.py deleted file mode 100644 index 5d7b0038..00000000 --- a/shuffle-tools-fork/1.0.0/src/app.py +++ /dev/null @@ -1,184 +0,0 @@ -import hmac -import datetime -import json -import time -import markupsafe -import os -import re -import subprocess -import tempfile -import zipfile -import base64 -import importlib -import ipaddress -import hashlib -import shufflepy -from io import StringIO -from contextlib import redirect_stdout -import random -import string - -import xmltodict -from json2xml import json2xml -from json2xml.utils import readfromstring - -from ioc_finder import find_iocs -from dateutil.parser import parse as dateutil_parser -from google.auth import crypt -from google.auth import jwt - -import py7zr -import pyminizip -import rarfile -import requests -import tarfile -import binascii -import struct - -import paramiko -import concurrent.futures -import multiprocessing - -from pip._internal import main as pip_main -from pip._internal.commands.show import search_packages_info - -from walkoff_app_sdk.app_base import AppBase - -class Tools(AppBase): - __version__ = "1.2.0" - app_name = ( - "Shuffle Tools Fork" # this needs to match "name" in api.yaml for WALKOFF to work - ) - - def __init__(self, redis, logger, console_logger=None): - """ - Each app should have this __init__ to set up Redis and logging. - :param redis: - :param logger: - :param console_logger: - """ - super().__init__(redis, logger, console_logger) - - def dynamic_import(package_name: str): - """Import a package and return the module""" - return importlib.import_module(package_name.split('==')[0].split('>=')[0].split('<=')[0].split('>')[0].split('<')[0]) - - - def get_missing_packages(required_packages: list) -> list: - """ - Returns a list of packages that aren't currently installed. - - Args: - required_packages: List of package names (can include version specs) - - Returns: - List of package names that aren't installed - """ - missing = [] - for package in required_packages: - # Remove version specifiers if present (e.g., 'pandas>=1.0.0' -> 'pandas') - package_name = package.split('==')[0].split('>=')[0].split('<=')[0].split('>')[0].split('<')[0].strip() - - # Check if package exists in environment - if not list(search_packages_info([package_name])): - missing.append(package) - - return missing - - def install_packages(self, packages=[]) -> None: - """ - Install Python packages using pip's Python interface. - - Args: - packages: List of package names to install - """ - - packages_not_found = self.get_missing_packages(packages) - - for package in packages_not_found: - try: - pip_main(['install', package]) - print(f"Successfully installed {package}") - except Exception as e: - print(f"Failed to install {package}: {str(e)}") - - def execute_python(self, code, packages) -> dict: - if os.getenv("SHUFFLE_ALLOW_PACKAGE_INSTALL") == "true": - allow_package_install = True - - packages = packages.split("\n") if packages else [] - - if packages: - if allow_package_install: - self.install_packages(packages) - self.dynamic_import(packages) - - if len(code) == 36 and "-" in code: - filedata = self.get_file(code) - if filedata["success"] == False: - return { - "success": False, - "message": f"Failed to get file for ID {code}", - } - - if ".py" not in filedata["filename"]: - return { - "success": False, - "message": f"Filename needs to contain .py", - } - - - # Write the code to a file - # 1. Take the data into a file - # 2. Subprocess execute file? - try: - f = StringIO() - def custom_print(*args, **kwargs): - return print(*args, file=f, **kwargs) - - #with redirect_stdout(f): # just in case - # Add globals in it too - globals_copy = globals().copy() - globals_copy["print"] = custom_print - - # Add self to globals_copy - for key, value in locals().copy().items(): - if key not in globals_copy: - globals_copy[key] = value - - globals_copy["self"] = self - - exec(code, globals_copy) - - s = f.getvalue() - f.close() # why: https://www.youtube.com/watch?v=6SA6S9Ca5-U - - #try: - # s = s.encode("utf-8") - #except Exception as e: - - try: - return { - "success": True, - "message": json.loads(s.strip()), - } - except Exception as e: - try: - return { - "success": True, - "message": s.strip(), - } - except Exception as e: - return { - "success": True, - "message": s, - } - - except Exception as e: - return { - "success": False, - "message": f"exception: {e}", - } - -if __name__ == "__main__": - Tools.run() diff --git a/shuffle-tools-fork/1.0.0/src/concurrency.py b/shuffle-tools-fork/1.0.0/src/concurrency.py deleted file mode 100644 index 420d1686..00000000 --- a/shuffle-tools-fork/1.0.0/src/concurrency.py +++ /dev/null @@ -1,201 +0,0 @@ -import time -import json -import ipaddress -import concurrent.futures -from functools import partial -from ioc_finder import find_iocs - -class Test(): - def split_text(self, text): - # Split text into chunks of 10kb. Add each 10k to array - # In case e.g. 1.2.3.4 lands exactly on 20k boundary, it may be useful to overlap here. - # (just shitty code to reduce chance of issues) while still going fast - - arr_one = [] - max_len = 2500 - current_string = "" - overlaps = 100 - - - for i in range(0, len(text)): - current_string += text[i] - if len(current_string) > max_len: - # Appending just in case even with overlaps - if len(text) > i+overlaps: - current_string += text[i+1:i+overlaps] - else: - current_string += text[i+1:] - - arr_one.append(current_string) - current_string = "" - - if len(current_string) > 0: - arr_one.append(current_string) - - #print("DATA:", arr_one) - print("Strings:", len(arr_one)) - #exit() - - return arr_one - - def _format_result(self, result): - final_result = {} - - for res in result: - for key, val in res.items(): - if key in final_result: - if isinstance(val, list) and len(val) > 0: - for i in val: - final_result[key].append(i) - elif isinstance(val, dict): - #print(key,":::",val) - if key in final_result: - if isinstance(val, dict): - for k,v in val.items(): - #print("k:",k,"v:",v) - val[k].append(v) - #print(val) - #final_result[key].append([i for i in val if len(val) > 0]) - else: - final_result[key] = val - - return final_result - - def worker_function(self, inputdata): - return find_iocs(inputdata["data"], included_ioc_types=inputdata["ioc_types"]) - - def _with_concurency(self, array_of_strings, ioc_types): - results = [] - #start = time.perf_counter() - - # Workers dont matter..? - # What can we use instead? - - results = [] - workers = 4 - with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as executor: - # Submit the find_iocs function for each string in the array - futures = [executor.submit( - find_iocs, - text=string, - included_ioc_types=ioc_types, - ) for string in array_of_strings] - - # Wait for all tasks to complete - concurrent.futures.wait(futures) - - # Retrieve the results if needed - results = [future.result() for future in futures] - - return self._format_result(results) - - def parse_ioc_new(self, input_string, input_type="all"): - if input_type == "": - input_type = "all" - - #ioc_types = ["domains", "urls", "email_addresses", "ipv6s", "ipv4s", "ipv4_cidrs", "md5s", "sha256s", "sha1s", "cves"] - ioc_types = ["domains", "urls", "email_addresses", "ipv4s", "ipv4_cidrs", "md5s", "sha256s", "sha1s", "cves"] - - # urls = 10.4 -> 9.1 - # emails = 10.4 -> 9.48 - # ipv6s = 10.4 -> 7.37 - # ipv4 cidrs = 10.4 -> 10.44 - - if input_type == "" or input_type == "all": - ioc_types = ioc_types - else: - input_type = input_type.split(",") - for item in input_type: - item = item.strip() - - ioc_types = input_type - - input_string = str(input_string) - if len(input_string) > 10000: - iocs = self._with_concurency(self.split_text(input_string), ioc_types=ioc_types) - else: - iocs = find_iocs(input_string, included_ioc_types=ioc_types) - - newarray = [] - for key, value in iocs.items(): - if input_type != "all": - if key not in input_type: - continue - - if len(value) == 0: - continue - - for item in value: - # If in here: attack techniques. Shouldn't be 3 levels so no - # recursion necessary - if isinstance(value, dict): - for subkey, subvalue in value.items(): - if len(subvalue) == 0: - continue - - for subitem in subvalue: - data = { - "data": subitem, - "data_type": "%s_%s" % (key[:-1], subkey), - } - - if data not in newarray: - newarray.append(data) - else: - data = {"data": item, "data_type": key[:-1]} - if data not in newarray: - newarray.append(data) - - # Reformatting IP - i = -1 - for item in newarray: - i += 1 - if "ip" not in item["data_type"]: - continue - - newarray[i]["data_type"] = "ip" - try: - newarray[i]["is_private_ip"] = ipaddress.ip_address(item["data"]).is_private - except Exception as e: - print("Error parsing %s: %s" % (item["data"], e)) - - try: - newarray = json.dumps(newarray) - except json.decoder.JSONDecodeError as e: - return "Failed to parse IOC's: %s" % e - - return newarray - -# Make it not run this for multithreads -if __name__ == "__main__": - - input_string = "" - with open("testdata.txt", "r") as f: - input_string = f.read() - - try: - json_data = json.loads(input_string) - # If array, loop - if isinstance(json_data, list): - cnt = 0 - start = time.perf_counter() - for item in json_data: - cnt += 1 - classdata = Test() - - ret = classdata.parse_ioc_new(item) - #print("OUTPUT1: ", ret) - - #if cnt == 5: - # break - - print("Total time taken:", time.perf_counter()-start) - else: - classdata = Test() - ret = classdata.parse_ioc_new(input_string) - print("OUTPUT2: ", ret) - except Exception as e: - classdata = Test() - ret = classdata.parse_ioc_new(json_data) - print("OUTPUT3: ", ret) - diff --git a/shuffle-tools-fork/1.0.0/src/switch.py b/shuffle-tools-fork/1.0.0/src/switch.py deleted file mode 100644 index 78ede505..00000000 --- a/shuffle-tools-fork/1.0.0/src/switch.py +++ /dev/null @@ -1,203 +0,0 @@ -# self, sourcevalue, condition, destinationvalue -def validate_condition(sourcevalue, check, destinationvalue): - if check == "=" or check == "==" or check.lower() == "equals": - if str(sourcevalue).lower() == str(destinationvalue).lower(): - return True - elif check == "!=" or check.lower() == "does not equal": - if str(sourcevalue).lower() != str(destinationvalue).lower(): - return True - elif check.lower() == "startswith": - if str(sourcevalue).lower().startswith(str(destinationvalue).lower()): - return True - - - elif check.lower() == "endswith": - if str(sourcevalue).lower().endswith(str(destinationvalue).lower()): - return True - elif check.lower() == "contains": - if destinationvalue.lower() in sourcevalue.lower(): - return True - - elif check.lower() == "is empty" or check.lower() == "is_empty": - try: - if len(json.loads(sourcevalue)) == 0: - return True - except Exception as e: - print("[ERROR] Failed to check if empty as list: {e}") - - if len(str(sourcevalue)) == 0: - return True - - elif check.lower() == "contains_any_of": - newvalue = [destinationvalue.lower()] - if "," in destinationvalue: - newvalue = destinationvalue.split(",") - elif ", " in destinationvalue: - newvalue = destinationvalue.split(", ") - - for item in newvalue: - if not item: - continue - - if item.strip() in sourcevalue: - return True - - elif check.lower() == "larger than" or check.lower() == "bigger than" or check == ">" or check == ">=": - try: - if str(sourcevalue).isdigit() and str(destinationvalue).isdigit(): - if int(sourcevalue) > int(destinationvalue): - return True - - except AttributeError as e: - print("[WARNING] Condition larger than failed with values %s and %s: %s" % (sourcevalue, destinationvalue, e)) - - try: - destinationvalue = len(json.loads(destinationvalue)) - except Exception as e: - print("[WARNING] Failed to convert destination to list: {e}") - try: - # Check if it's a list in autocast and if so, check the length - if len(json.loads(sourcevalue)) > int(destinationvalue): - return True - except Exception as e: - print("[WARNING] Failed to check if larger than as list: {e}") - - - elif check.lower() == "smaller than" or check.lower() == "less than" or check == "<" or check == "<=": - print("In smaller than check: %s %s" % (sourcevalue, destinationvalue)) - - try: - if str(sourcevalue).isdigit() and str(destinationvalue).isdigit(): - if int(sourcevalue) < int(destinationvalue): - return True - - except AttributeError as e: - pass - - try: - destinationvalue = len(json.loads(destinationvalue)) - except Exception as e: - print("[WARNING] Failed to convert destination to list: {e}") - - try: - # Check if it's a list in autocast and if so, check the length - if len(json.loads(sourcevalue)) < int(destinationvalue): - return True - except Exception as e: - print("[WARNING] Failed to check if smaller than as list: {e}") - - elif check.lower() == "re" or check.lower() == "matches regex": - try: - found = re.search(str(destinationvalue), str(sourcevalue)) - except re.error as e: - return False - except Exception as e: - return False - - if found == None: - return False - - return True - else: - print("[DEBUG] Condition: can't handle %s yet. Setting to true" % check) - - return False - -def evaluate_conditions(condition_structure): - operator = condition_structure.get('operator') - - # Base case: Single condition - if 'source' in condition_structure: - source = condition_structure['source'] - condition = condition_structure['condition'] - destination = condition_structure['destination'] - - # self. - return validate_condition(source, condition, destination) - - # Recursive case: Logical operator - elif operator == "AND": - return all(evaluate_conditions(sub_condition) for sub_condition in condition_structure['conditions']) - - elif operator == "OR": - return any(evaluate_conditions(sub_condition) for sub_condition in condition_structure['conditions']) - - elif operator == "NOT": - return not evaluate_conditions(condition_structure['conditions'][0]) - - else: - raise ValueError(f"Unknown operator: {operator}") - - -def switch(conditions): - to_return = { - "success": True, - "run_else": True, - } - - for condition in conditions: - if "id" not in condition: - print("Condition ID not found") - continue - - evaluated = False - try: - evaluated = evaluate_conditions(condition) - except Exception as e: - print(f"Failed to evaluate condition {condition['id']}: {e}") - - if evaluated == True: - to_return["run_else"] = False - - to_return[condition["id"]] = evaluated - - return to_return - -# Example usage - -condition_structure = { - "id": "lol", - "operator": "AND", - "conditions": [ - { # true - "source": "20", # age - "condition": ">", - "destination": 18 - }, - { # true - "operator": "OR", - "conditions": [ - { - "source": "active", # status - "condition": "==", - "destination": "active" - }, - { - "source": "1500", # balance - "condition": ">=", - "destination": 1000 - } - ] - }, - { - "operator": "NOT", - "conditions": [ - { - "source": "user", # user - "condition": "==", - "destination": "admin" - } - ] - } - ] -} - -newcondition = condition_structure.copy() -testconditions = [condition_structure] -newcondition['id'] = "lol2" -testconditions.append(newcondition) - -result = switch(testconditions) -print() -print() -print("Output: ", result) From 655b987106efa29239d7d188dad5dd9a7e2f1353 Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 6 Jan 2025 13:12:59 +0100 Subject: [PATCH 153/259] Fixed the CIDR range match function to automatically fix any errors in CIDRs --- shuffle-tools/1.2.0/src/app.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 84401e1c..7fa24fff 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -2182,8 +2182,17 @@ def convert_json_to_tags(self, json_object, split_value=", ", include_key=True, return fullstring - def cidr_ip_match(self, ip, networks): + def autofix_network(self, ip_with_cidr): + try: + # Parse the input as an IPv4 network object + network = ipaddress.IPv4Network(ip_with_cidr, strict=False) + # Return the corrected network address + return str(network) + except ValueError as e: + print(f"Error: {e}") + return None + def cidr_ip_match(self, ip, networks): if isinstance(networks, str): try: networks = json.loads(networks) @@ -2193,9 +2202,18 @@ def cidr_ip_match(self, ip, networks): "reason": "Networks is not a valid list: {}".format(networks), } + new_networks = [] + for network in networks: + new_network = self.autofix_network(network) + if new_network: + new_networks.append(new_network) + + networks = new_networks + try: ip_networks = list(map(ipaddress.ip_network, networks)) - ip_address = ipaddress.ip_address(ip, False) + #ip_address = ipaddress.ip_address(ip, False) + ip_address = ipaddress.ip_address(ip) except ValueError as e: return "IP or some networks are not in valid format.\nError: {}".format(e) From a70a0df5a8909c1ac536184966d6d259b835cd44 Mon Sep 17 00:00:00 2001 From: yashsinghcodes Date: Tue, 7 Jan 2025 15:19:10 +0530 Subject: [PATCH 154/259] removed debug log --- shuffle-tools/1.2.0/src/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 7fa24fff..ad11adb1 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -1920,7 +1920,7 @@ def check_cache_contains(self, key, value, append): try: for item in parsedvalue: #return "%s %s" % (item, value) - self.logger.info(f"{item} == {value}") + #self.logger.info(f"{item} == {value}") if str(item) == str(value): if not append: try: From aaf15d2b5bcc9f23f9e460b3a345476c23d4e4c3 Mon Sep 17 00:00:00 2001 From: yashsinghcodes Date: Tue, 7 Jan 2025 20:25:03 +0530 Subject: [PATCH 155/259] [fix]: check_cache_contains failing due to shared cache --- shuffle-tools/1.2.0/src/app.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index ad11adb1..934cd364 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -1807,6 +1807,7 @@ def escape_html(self, input_data): def check_cache_contains(self, key, value, append): org_id = self.full_execution["workflow"]["execution_org"]["id"] + url = "%s/api/v1/orgs/%s/get_cache" % (self.url, org_id) data = { "workflow_id": self.full_execution["workflow"]["id"], "execution_id": self.current_execution_id, @@ -1855,6 +1856,10 @@ def check_cache_contains(self, key, value, append): #allvalues = get_response.json() allvalues = self.shared_cache + if "success" not in allvalues: + get_response = requests.post(url, json=data, verify=False) + allvalues = get_response.json() + try: if allvalues["value"] == None or allvalues["value"] == "null": allvalues["value"] = "[]" From be8c311307fd6e1e35cee12282d0424c0d1f050c Mon Sep 17 00:00:00 2001 From: yashsinghcodes Date: Sat, 11 Jan 2025 01:38:02 +0530 Subject: [PATCH 156/259] fixed list condition check --- shuffle-tools/1.2.0/src/app.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 934cd364..ef7eb955 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -1816,6 +1816,7 @@ def check_cache_contains(self, key, value, append): "search": str(value), "key": key, } + directcall = False allvalues = {} try: @@ -1859,6 +1860,7 @@ def check_cache_contains(self, key, value, append): if "success" not in allvalues: get_response = requests.post(url, json=data, verify=False) allvalues = get_response.json() + directcall = True try: if allvalues["value"] == None or allvalues["value"] == "null": @@ -1977,11 +1979,13 @@ def check_cache_contains(self, key, value, append): if value not in allvalues["value"] and isinstance(allvalues["value"], list): self.cache_update_buffer.append(value) allvalues["value"].append(value) - #set_url = "%s/api/v1/orgs/%s/set_cache" % (self.url, org_id) - #response = requests.post(set_url, json=data, verify=False) exception = "" try: - #allvalues = response.json() + if directcall: + set_url = "%s/api/v1/orgs/%s/set_cache" % (self.url, org_id) + response = requests.post(set_url, json=data, verify=False) + allvalues = response.json() + #return allvalues return { From 788aa3497d51ac74d0f137090de463d8b45f0a96 Mon Sep 17 00:00:00 2001 From: yashsinghcodes Date: Sat, 11 Jan 2025 02:06:50 +0530 Subject: [PATCH 157/259] [fix]: Minor bugs in check_cache --- shuffle-tools/1.2.0/run.sh | 4 ++-- shuffle-tools/1.2.0/src/app.py | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) mode change 100644 => 100755 shuffle-tools/1.2.0/run.sh diff --git a/shuffle-tools/1.2.0/run.sh b/shuffle-tools/1.2.0/run.sh old mode 100644 new mode 100755 index aaf4edf7..3480d2f8 --- a/shuffle-tools/1.2.0/run.sh +++ b/shuffle-tools/1.2.0/run.sh @@ -1,7 +1,7 @@ # Build testing -NAME=frikky/shuffle:shuffle-tools_1.1.0 +NAME=frikky/shuffle:shuffle-tools_1.2.0 docker rmi $NAME --force -docker build . -t frikky/shuffle:shuffle-tools_1.1.0 +docker build . -t frikky/shuffle:shuffle-tools_1.2.0 # Run testing #docker run -e SHUFFLE_SWARM_CONFIG=run -e SHUFFLE_APP_EXPOSED_PORT=33334 frikky/shuffle:shuffle-tools_1.1.0 diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index ef7eb955..01af7e34 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -1981,7 +1981,14 @@ def check_cache_contains(self, key, value, append): allvalues["value"].append(value) exception = "" try: + # FIXME: This is a hack, but it works if directcall: + new_value = parsedvalue + if new_value == None: + new_value = [value] + + data["value"] = json.dumps(new_value) + set_url = "%s/api/v1/orgs/%s/set_cache" % (self.url, org_id) response = requests.post(set_url, json=data, verify=False) allvalues = response.json() @@ -1991,7 +1998,7 @@ def check_cache_contains(self, key, value, append): return { "success": True, "found": False, - "reason": "Appended as it didn't exist", + "reason": f"Appended as it didn't exist", "key": key, "search": value, "value": parsedvalue, From 067f4e820103f34771e95530a84ef82e254a1caf Mon Sep 17 00:00:00 2001 From: Frikky Date: Sun, 12 Jan 2025 14:44:54 +0100 Subject: [PATCH 158/259] Email app fix for deduped mails --- email/1.3.0/requirements.txt | 6 ++--- email/1.3.0/src/app.py | 46 +++++++++++++++++++++++++++++------- 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/email/1.3.0/requirements.txt b/email/1.3.0/requirements.txt index 926027e8..6ee8846d 100644 --- a/email/1.3.0/requirements.txt +++ b/email/1.3.0/requirements.txt @@ -1,8 +1,6 @@ requests==2.25.1 glom==20.11.0 -eml-parser==1.17.0 -msg-parser==1.2.0 -mail-parser==3.15.0 -extract-msg==0.30.9 jsonpickle==2.0.0 +eml-parser==2.0.0 +msg-parser==1.2.0 diff --git a/email/1.3.0/src/app.py b/email/1.3.0/src/app.py index 588863dc..528af89b 100644 --- a/email/1.3.0/src/app.py +++ b/email/1.3.0/src/app.py @@ -11,8 +11,6 @@ import time import random import eml_parser -import mailparser -import extract_msg import jsonpickle from glom import glom @@ -392,6 +390,18 @@ def merge(d1, d2): "messages": json.dumps(emails, default=default), } + def remove_similar_items(self, items): + # Sort items by length in descending order + items = sorted(items, key=len, reverse=True) + result = [] + + for domain in items: + # Check if the domain is part of any domain already in the result + if not any(domain in main for main in result): + result.append(domain) + + return result + def parse_eml(self, filedata, extract_attachments=False): parsedfile = { "success": True, @@ -443,16 +453,16 @@ def parse_email_file(self, file_id, extract_attachments=False): # Replace raw newlines \\r\\n with actual newlines # The data is a byte string, so we need to decode it to utf-8 try: - print("Pre size: %d" % len(file_path["data"])) + #print("Pre size: %d" % len(file_path["data"])) file_path["data"] = file_path["data"].decode("utf-8").replace("\\r\\n", "\n").encode("utf-8") - print("Post size: %d" % len(file_path["data"])) + #print("Post size: %d" % len(file_path["data"])) except Exception as e: print(f"Failed to decode file: {e}") pass # Makes msg into eml if ".msg" in file_path["filename"] or "." not in file_path["filename"]: - print(f"[DEBUG] Working with .msg file {file_path['filename']}. Filesize: {len(file_path['data'])}") + self.logger.info(f"[DEBUG] Working with .msg file {file_path['filename']}. Filesize: {len(file_path['data'])}") try: result = {} msg = MsOxMessage(file_path['data']) @@ -471,17 +481,17 @@ def parse_email_file(self, file_id, extract_attachments=False): ) try: - print("Pre email") + self.logger.info("Pre email") parsed_eml = ep.decode_email_bytes(file_path['data']) #if str(parsed_eml["header"]["date"]) == "1970-01-01 00:00:00+00:00" and len(parsed_eml["header"]["subject"]) == 0: # return {"success":False,"reason":"Not a valid EML/MSG file, or the file have a timestamp or subject defined (required).", "date": str(parsed_eml["header"]["date"]), "subject": str(parsed_eml["header"]["subject"])} # Put attachments in the shuffle file system - print("Pre attachment") + self.logger.info("Pre attachment") if extract_attachments == True and "attachment" in parsed_eml: cnt = -1 - print("[INFO] Uploading %d attachments" % len(parsed_eml["attachment"])) + self.logger.info("[INFO] Uploading %d attachments" % len(parsed_eml["attachment"])) for value in parsed_eml["attachment"]: cnt += 1 if value["raw"] == None: @@ -502,7 +512,25 @@ def parse_email_file(self, file_id, extract_attachments=False): if not "attachment" in parsed_eml: parsed_eml["attachment"] = [] - print("Post attachment") + self.logger.info("Post attachment. Has body: %s" % ("body" in parsed_eml)) + + try: + if "body" in parsed_eml and len(parsed_eml["body"]) > 0: + + for i in range(len(parsed_eml["body"])): + if "uri" in parsed_eml["body"][i] and len(parsed_eml["body"][i]["uri"]) > 0: + parsed_eml["body"][i]["uri"] = self.remove_similar_items(parsed_eml["body"][i]["uri"]) + + if "email" in parsed_eml["body"][i] and len(parsed_eml["body"][i]["email"]) > 0: + parsed_eml["body"][i]["email"] = self.remove_similar_items(parsed_eml["body"][i]["email"]) + + if "domain" in parsed_eml["body"][i] and len(parsed_eml["body"][i]["domain"]) > 0: + parsed_eml["body"][i]["domain"] = self.remove_similar_items(parsed_eml["body"][i]["domain"]) + + except Exception as e: + self.logger.info(f"[ERROR] Failed to remove similar items: {e}") + + parsed_eml["success"] = True return json.dumps(parsed_eml, default=json_serial) except Exception as e: return {"success":False, "reason": f"An exception occured during EML parsing: {e}. Please contact support"} From 64d42b3dfe3ffe39d9fd7cf13d30f3f9d532f67d Mon Sep 17 00:00:00 2001 From: Frikky Date: Sun, 12 Jan 2025 16:49:53 +0100 Subject: [PATCH 159/259] Fixed random exits from python code --- shuffle-tools/1.2.0/requirements.txt | 16 ++++++------- shuffle-tools/1.2.0/src/app.py | 36 ++++++++++++++++++++++++---- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/shuffle-tools/1.2.0/requirements.txt b/shuffle-tools/1.2.0/requirements.txt index 9c8ee043..856c5473 100644 --- a/shuffle-tools/1.2.0/requirements.txt +++ b/shuffle-tools/1.2.0/requirements.txt @@ -1,10 +1,10 @@ -ioc_finder==7.2.1 -py7zr==0.11.3 -rarfile==4.0 -pyminizip==0.2.4 -requests==2.25.1 -xmltodict==0.11.0 +ioc_finder==7.3.0 +py7zr==0.22.0 +rarfile==4.2 +pyminizip==0.2.6 +requests==2.32.3 +xmltodict==0.14.2 json2xml==5.0.5 ipaddress==1.0.23 -google.auth==1.23.0 -paramiko==3.1.0 +google.auth==2.37.0 +paramiko==3.5.0 diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 01af7e34..2126091d 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -1,9 +1,12 @@ +import os +import sys +import builtins + import hmac import datetime import json import time import markupsafe -import os import re import subprocess import tempfile @@ -41,6 +44,12 @@ from walkoff_app_sdk.app_base import AppBase #from shuffle_sdk import AppBase +# Override exit(), sys.exit, and os._exit +# sys.exit() can be caught, meaning we can have a custom handler for it +builtins.exit = sys.exit +os.exit = sys.exit +os._exit = sys.exit + class Tools(AppBase): __version__ = "1.2.0" app_name = ( @@ -573,7 +582,6 @@ def execute_python(self, code): "message": f"Filename needs to contain .py", } - # Write the code to a file # 1. Take the data into a file # 2. Subprocess execute file? @@ -594,7 +602,27 @@ def custom_print(*args, **kwargs): globals_copy["self"] = self - exec(code, globals_copy) + try: + exec(code, globals_copy) + except SystemExit as e: + # Same as a return + pass + except SyntaxError as e: + # Special handler for return usage. Makes return act as + # an exit() + if "'return' outside function" in str(e): + return { + "success": False, + "message": f"Instead of using 'return' without a function, use 'exit()' to return when not inside a function. Raw Syntax error: {e}", + } + else: + return { + "success": False, + "message": f"Syntax Error: {e}", + } + + # this doesn't work to capture top-level returns + # Reason: SyntaxError makes it crash BEFORE it reaches the return s = f.getvalue() f.close() # why: https://www.youtube.com/watch?v=6SA6S9Ca5-U @@ -623,7 +651,7 @@ def custom_print(*args, **kwargs): except Exception as e: return { "success": False, - "message": f"exception: {e}", + "message": f"Exception: {e}", } def execute_bash(self, code, shuffle_input): From fb6f8183d6ccc549a6dbcaa1a285748e24e07faf Mon Sep 17 00:00:00 2001 From: yashsinghcodes Date: Mon, 13 Jan 2025 16:01:50 +0530 Subject: [PATCH 160/259] remvoed gzip --- shuffle-tools/1.2.0/src/app.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 01af7e34..0f0191aa 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -701,7 +701,6 @@ def check_compression(self, obj, threshold=1_000_000): def compress_data(self, obj): data_btyes = json.dumps(obj).encode("utf-8") compressed_data = gzip.compress(data_btyes) - return base64.b64encode(compressed_data).decode("utf-8") def update_cache(self, key): org_id = self.full_execution["workflow"]["execution_org"]["id"] @@ -1003,9 +1002,9 @@ def filter_list(self, input_list, field, check, value, opposite): "valid": new_list, "invalid": failed_list, } - if self.check_compression(data): - data = self.compress_data(data) - return data +# if self.check_compression(data): +# data = self.compress_data(data) +# return data return json.dumps(data) # new_list = json.dumps(new_list) From 4a385153e52eaa9a554e1ed410da68347c19dc21 Mon Sep 17 00:00:00 2001 From: yashsinghcodes Date: Mon, 13 Jan 2025 16:06:19 +0530 Subject: [PATCH 161/259] removed the function completely --- shuffle-tools/1.2.0/src/app.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 0f0191aa..551a4c2e 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -692,15 +692,6 @@ def preload_cache(self, key): response_data["value"] = parsed return get_response.json() - def check_compression(self, obj, threshold=1_000_000): - data_btyes = json.dumps(obj).encode("utf-8") - if len(data_btyes) > threshold: - return True - return False - - def compress_data(self, obj): - data_btyes = json.dumps(obj).encode("utf-8") - compressed_data = gzip.compress(data_btyes) def update_cache(self, key): org_id = self.full_execution["workflow"]["execution_org"]["id"] From db7702937204125d5b5953fba541e905b5fd0fa9 Mon Sep 17 00:00:00 2001 From: yashsinghcodes Date: Mon, 13 Jan 2025 16:07:43 +0530 Subject: [PATCH 162/259] removed the comments as well --- shuffle-tools/1.2.0/src/app.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 551a4c2e..857419d4 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -993,9 +993,6 @@ def filter_list(self, input_list, field, check, value, opposite): "valid": new_list, "invalid": failed_list, } -# if self.check_compression(data): -# data = self.compress_data(data) -# return data return json.dumps(data) # new_list = json.dumps(new_list) From c46a0aeedae4222ffc173a2534a476dcf50d4721 Mon Sep 17 00:00:00 2001 From: yashsinghcodes Date: Tue, 14 Jan 2025 12:00:22 +0530 Subject: [PATCH 163/259] fixed a check_cache_contains failing when append is false --- shuffle-tools/1.2.0/src/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 857419d4..49779033 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -1956,7 +1956,7 @@ def check_cache_contains(self, key, value, append): "reason": "Not found, not appending (2)!", "key": key, "search": value, - "value": json.loads(allvalues["value"]), + "value": allvalues["value"], } #parsedvalue.append(value) From acb601f0cafdd583bb6002b09b4da5040e66b99b Mon Sep 17 00:00:00 2001 From: Frikky Date: Tue, 14 Jan 2025 12:26:57 +0100 Subject: [PATCH 164/259] Minor build change --- shuffle-tools/1.2.0/Dockerfile | 3 ++- shuffle-tools/1.2.0/src/app.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/shuffle-tools/1.2.0/Dockerfile b/shuffle-tools/1.2.0/Dockerfile index 5c1a8af4..29ddda0c 100644 --- a/shuffle-tools/1.2.0/Dockerfile +++ b/shuffle-tools/1.2.0/Dockerfile @@ -11,7 +11,8 @@ RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-de RUN mkdir /install WORKDIR /install COPY requirements.txt /requirements.txt -RUN pip install --no-cache-dir --prefix="/install" -r /requirements.txt +#RUN pip install --no-cache-dir --prefix="/install" -r /requirements.txt +RUN pip install --prefix="/install" -r /requirements.txt # Switch back to our base image and copy in all of our built packages and source code FROM base diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 2126091d..cca3c9c5 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -613,7 +613,7 @@ def custom_print(*args, **kwargs): if "'return' outside function" in str(e): return { "success": False, - "message": f"Instead of using 'return' without a function, use 'exit()' to return when not inside a function. Raw Syntax error: {e}", + "message": f"SyntaxError - Shuffle Recommendation: Instead of using 'return' without a function, use 'exit()' to return when not inside a function. Raw Syntax error: {e}", } else: return { From 092ef884533f44ecb1921ff2e5ab75aaf28137e1 Mon Sep 17 00:00:00 2001 From: Frikky Date: Tue, 21 Jan 2025 10:37:51 +0100 Subject: [PATCH 165/259] Fixed http app proxy bug --- http/1.4.0/src/app.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/http/1.4.0/src/app.py b/http/1.4.0/src/app.py index 865d223e..bfec85a3 100755 --- a/http/1.4.0/src/app.py +++ b/http/1.4.0/src/app.py @@ -174,7 +174,11 @@ def GET(self, url, headers="", username="", password="", verify=True, http_proxy parsed_headers = self.splitheaders(headers) parsed_headers["User-Agent"] = "Shuffle Automation" verify = self.checkverify(verify) + proxies = None + if http_proxy or https_proxy: + proxies = {} + if http_proxy: proxies["http"] = http_proxy if https_proxy: @@ -213,6 +217,8 @@ def POST(self, url, headers="", body="", username="", password="", verify=True, verify = self.checkverify(verify) body = self.checkbody(body) proxies = None + if http_proxy or https_proxy: + proxies = {} if http_proxy: proxies["http"] = http_proxy if https_proxy: @@ -251,6 +257,8 @@ def PUT(self, url, headers="", body="", username="", password="", verify=True, h verify = self.checkverify(verify) body = self.checkbody(body) proxies = None + if http_proxy or https_proxy: + proxies = {} if http_proxy: proxies["http"] = http_proxy if https_proxy: @@ -290,6 +298,8 @@ def PATCH(self, url, headers="", body="", username="", password="", verify=True, verify = self.checkverify(verify) body = self.checkbody(body) proxies = None + if http_proxy or https_proxy: + proxies = {} if http_proxy: proxies["http"] = http_proxy if https_proxy: @@ -328,6 +338,8 @@ def DELETE(self, url, headers="", body="", username="", password="", verify=True verify = self.checkverify(verify) body = self.checkbody(body) proxies = None + if http_proxy or https_proxy: + proxies = {} if http_proxy: proxies["http"] = http_proxy if https_proxy: @@ -366,6 +378,8 @@ def HEAD(self, url, headers="", body="", username="", password="", verify=True, verify = self.checkverify(verify) body = self.checkbody(body) proxies = None + if http_proxy or https_proxy: + proxies = {} if http_proxy: proxies["http"] = http_proxy if https_proxy: @@ -404,6 +418,8 @@ def OPTIONS(self, url, headers="", body="", username="", password="", verify=Tru verify = self.checkverify(verify) body = self.checkbody(body) proxies = None + if http_proxy or https_proxy: + proxies = {} if http_proxy: proxies["http"] = http_proxy if https_proxy: From 0b7ed8631259bfd6bd083feed11a9bbf300f1e5f Mon Sep 17 00:00:00 2001 From: Frikky Date: Tue, 21 Jan 2025 10:52:28 +0100 Subject: [PATCH 166/259] Fixed HTTP app proxy --- http/1.4.0/src/app.py | 29 +++++++---------------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/http/1.4.0/src/app.py b/http/1.4.0/src/app.py index bfec85a3..981c3cc8 100755 --- a/http/1.4.0/src/app.py +++ b/http/1.4.0/src/app.py @@ -175,10 +175,7 @@ def GET(self, url, headers="", username="", password="", verify=True, http_proxy parsed_headers["User-Agent"] = "Shuffle Automation" verify = self.checkverify(verify) - proxies = None - if http_proxy or https_proxy: - proxies = {} - + proxies = {} if http_proxy: proxies["http"] = http_proxy if https_proxy: @@ -216,9 +213,7 @@ def POST(self, url, headers="", body="", username="", password="", verify=True, parsed_headers["User-Agent"] = "Shuffle Automation" verify = self.checkverify(verify) body = self.checkbody(body) - proxies = None - if http_proxy or https_proxy: - proxies = {} + proxies = {} if http_proxy: proxies["http"] = http_proxy if https_proxy: @@ -256,9 +251,7 @@ def PUT(self, url, headers="", body="", username="", password="", verify=True, h parsed_headers["User-Agent"] = "Shuffle Automation" verify = self.checkverify(verify) body = self.checkbody(body) - proxies = None - if http_proxy or https_proxy: - proxies = {} + proxies = {} if http_proxy: proxies["http"] = http_proxy if https_proxy: @@ -297,9 +290,7 @@ def PATCH(self, url, headers="", body="", username="", password="", verify=True, parsed_headers["User-Agent"] = "Shuffle Automation" verify = self.checkverify(verify) body = self.checkbody(body) - proxies = None - if http_proxy or https_proxy: - proxies = {} + proxies = {} if http_proxy: proxies["http"] = http_proxy if https_proxy: @@ -337,9 +328,7 @@ def DELETE(self, url, headers="", body="", username="", password="", verify=True parsed_headers["User-Agent"] = "Shuffle Automation" verify = self.checkverify(verify) body = self.checkbody(body) - proxies = None - if http_proxy or https_proxy: - proxies = {} + proxies = {} if http_proxy: proxies["http"] = http_proxy if https_proxy: @@ -377,9 +366,7 @@ def HEAD(self, url, headers="", body="", username="", password="", verify=True, parsed_headers["User-Agent"] = "Shuffle Automation" verify = self.checkverify(verify) body = self.checkbody(body) - proxies = None - if http_proxy or https_proxy: - proxies = {} + proxies = {} if http_proxy: proxies["http"] = http_proxy if https_proxy: @@ -417,9 +404,7 @@ def OPTIONS(self, url, headers="", body="", username="", password="", verify=Tru parsed_headers["User-Agent"] = "Shuffle Automation" verify = self.checkverify(verify) body = self.checkbody(body) - proxies = None - if http_proxy or https_proxy: - proxies = {} + proxies = {} if http_proxy: proxies["http"] = http_proxy if https_proxy: From 578a7ee0fc048ed73ab8adf89f5eef0786b54c6b Mon Sep 17 00:00:00 2001 From: yashsinghcodes Date: Wed, 22 Jan 2025 17:03:19 +0530 Subject: [PATCH 167/259] [FIX]: fixed the value return --- shuffle-tools/1.2.0/src/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 2f6cc351..913c327a 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -1998,7 +1998,7 @@ def check_cache_contains(self, key, value, append): try: # FIXME: This is a hack, but it works if directcall: - new_value = parsedvalue + new_value = allvalues["value"] if new_value == None: new_value = [value] @@ -2016,7 +2016,7 @@ def check_cache_contains(self, key, value, append): "reason": f"Appended as it didn't exist", "key": key, "search": value, - "value": parsedvalue, + "value": data["value"], } except Exception as e: exception = e From 27e41dd47ab50fb957de964cdcbc884730bd19d4 Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 23 Jan 2025 23:19:50 +0100 Subject: [PATCH 168/259] Minor optimizations for standalone runner --- shuffle-tools/1.2.0/requirements.txt | 1 + shuffle-tools/1.2.0/src/app.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/shuffle-tools/1.2.0/requirements.txt b/shuffle-tools/1.2.0/requirements.txt index 856c5473..fdb47196 100644 --- a/shuffle-tools/1.2.0/requirements.txt +++ b/shuffle-tools/1.2.0/requirements.txt @@ -8,3 +8,4 @@ json2xml==5.0.5 ipaddress==1.0.23 google.auth==2.37.0 paramiko==3.5.0 +shuffle-sdk==0.0.6 diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index fe37e9ad..ed4b3503 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -41,8 +41,8 @@ import concurrent.futures import multiprocessing -from walkoff_app_sdk.app_base import AppBase -#from shuffle_sdk import AppBase +#from walkoff_app_sdk.app_base import AppBase +from shuffle_sdk import AppBase # Override exit(), sys.exit, and os._exit # sys.exit() can be caught, meaning we can have a custom handler for it From 9f1c736e617c16336192e72ce0ce03b53c665395 Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 24 Jan 2025 02:34:28 +0100 Subject: [PATCH 169/259] Bumped Shuffle Tools to use the actual SDK and not required walkoff app sdk anymore. Will be a fun experiment :) --- shuffle-tools/1.2.0/requirements.txt | 2 +- shuffle-tools/1.2.0/src/app.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/shuffle-tools/1.2.0/requirements.txt b/shuffle-tools/1.2.0/requirements.txt index fdb47196..b5c8fb09 100644 --- a/shuffle-tools/1.2.0/requirements.txt +++ b/shuffle-tools/1.2.0/requirements.txt @@ -8,4 +8,4 @@ json2xml==5.0.5 ipaddress==1.0.23 google.auth==2.37.0 paramiko==3.5.0 -shuffle-sdk==0.0.6 +shuffle-sdk==0.0.8 diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 2eae3672..cb7c2da1 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -594,6 +594,8 @@ def custom_print(*args, **kwargs): # Add globals in it too globals_copy = globals().copy() globals_copy["print"] = custom_print + globals_copy["singul"] = self.singul + globals_copy["shuffle"] = self.singul # Add self to globals_copy for key, value in locals().copy().items(): From e088c033851ec524cac61035da4b0fd7e967a7aa Mon Sep 17 00:00:00 2001 From: Frikky Date: Sat, 25 Jan 2025 02:37:36 +0100 Subject: [PATCH 170/259] Returned shuffle tools back to walkoff app sdk --- shuffle-tools/1.2.0/src/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index cb7c2da1..fee87ab2 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -41,8 +41,8 @@ import concurrent.futures import multiprocessing -#from walkoff_app_sdk.app_base import AppBase -from shuffle_sdk import AppBase +from walkoff_app_sdk.app_base import AppBase +#from shuffle_sdk import AppBase # Override exit(), sys.exit, and os._exit # sys.exit() can be caught, meaning we can have a custom handler for it From b3c7fb2a24b76c9b0e7309101491f8f427c9c97b Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 27 Jan 2025 13:43:20 +0100 Subject: [PATCH 171/259] Removed singul requirement for execute_python --- shuffle-tools/1.2.0/src/app.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index fee87ab2..1b69e15a 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -594,8 +594,12 @@ def custom_print(*args, **kwargs): # Add globals in it too globals_copy = globals().copy() globals_copy["print"] = custom_print - globals_copy["singul"] = self.singul - globals_copy["shuffle"] = self.singul + try: + globals_copy["singul"] = self.singul + globals_copy["shuffle"] = self.singul + except Exception as e: + self.logger.info(f"Failed to add singul to python globals: {e}") + # Add self to globals_copy for key, value in locals().copy().items(): From 0215a54614ac7b6bbec131b4e2d26520dcf16326 Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 29 Jan 2025 12:00:50 +0100 Subject: [PATCH 172/259] Shuffle AI rebuild attempt --- shuffle-ai/1.0.0/Dockerfile | 55 +++++++++++++++-------- shuffle-ai/1.0.0/api.yaml | 13 +++++- shuffle-ai/1.0.0/requirements.txt | 2 + shuffle-ai/1.0.0/src/app.py | 72 +++++++++++++++++++++++++++++-- 4 files changed, 119 insertions(+), 23 deletions(-) diff --git a/shuffle-ai/1.0.0/Dockerfile b/shuffle-ai/1.0.0/Dockerfile index b4362193..18920086 100644 --- a/shuffle-ai/1.0.0/Dockerfile +++ b/shuffle-ai/1.0.0/Dockerfile @@ -1,26 +1,23 @@ -# Base our app image off of the WALKOFF App SDK image -FROM frikky/shuffle:app_sdk as base - -# We're going to stage away all of the bloat from the build tools so lets create a builder stage -FROM base as builder +FROM python:3.10-slim # Install all alpine build tools needed for our pip installs -RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev git poppler-utils +RUN apt update +RUN apt install -y clang g++ make automake autoconf libtool cmake # Install all of our pip packages in a single directory that we can copy to our base image later RUN mkdir /install WORKDIR /install # Switch back to our base image and copy in all of our built packages and source code -FROM base -COPY --from=builder /install /usr/local +#COPY --from=builder /install /usr/local COPY src /app COPY requirements.txt /requirements.txt RUN python3 -m pip install -r /requirements.txt # Install any binary dependencies needed in our final image # RUN apk --no-cache add --update my_binary_dependency -RUN apk --no-cache add jq git curl +#RUN apk --no-cache add jq git curl +RUN apt install -y jq git curl ENV SHELL=/bin/bash @@ -32,23 +29,45 @@ ENV TESSDATA_PREFIX=/usr/local/share/tessdata # Dev tools WORKDIR /tmp -RUN apk update -RUN apk upgrade -RUN apk add file openssl openssl-dev bash tini leptonica-dev openjpeg-dev tiff-dev libpng-dev zlib-dev libgcc mupdf-dev jbig2dec-dev -RUN apk add freetype-dev openblas-dev ffmpeg-dev linux-headers aspell-dev aspell-en # enchant-dev jasper-dev -RUN apk add --virtual .dev-deps git clang clang-dev g++ make automake autoconf libtool pkgconfig cmake ninja -RUN apk add --virtual .dev-testing-deps -X http://dl-3.alpinelinux.org/alpine/edge/testing autoconf-archive +#RUN apk update +#RUN apk upgrade + +## Install the same packages with apt as with apk, but ensure they exist in apt +RUN apt install -y file openssl bash tini libpng-dev aspell-en +RUN apt install -y git clang g++ make automake autoconf libtool cmake +RUN apt install -y autoconf-archive wget + RUN ln -s /usr/include/locale.h /usr/include/xlocale.h -RUN apk add tesseract-ocr -RUN apk add poppler-utils +#RUN apk add tesseract-ocr +RUN apt install -y tesseract-ocr +#RUN apk add poppler-utils +RUN apt install -y poppler-utils +RUN apt clean && rm -rf /var/lib/apt/lists/* # Install from main RUN mkdir /usr/local/share/tessdata +RUN wget https://github.com/tesseract-ocr/tessdata_fast/raw/main/eng.traineddata -P /usr/local/share/tessdata + RUN mkdir src RUN cd src -RUN wget https://github.com/tesseract-ocr/tessdata_fast/raw/main/eng.traineddata -P /usr/local/share/tessdata + RUN git clone --depth 1 https://github.com/tesseract-ocr/tesseract.git + +#RUN curl -fsSL https://ollama.com/install.sh | sh +# Install to /usr/local +RUN wget https://ollama.com/install.sh -O /usr/local/bin/ollama-install +RUN chmod +x /usr/local/bin/ollama-install +RUN ls /usr/local/bin +RUN sh /usr/local/bin/ollama-install + +RUN ls -alh /usr/bin +RUN which ollama + +#RUN /usr/local/bin/ollama pull llama3.2 +RUN ollama serve & sleep 2 && ollama pull llama3 + +#RUN rm /usr/local/bin/ollama #RUN cd tesseract && ./autogen.sh && ./configure --build=x86_64-alpine-linux-musl --host=x86_64-alpine-linux-musl && make && make install && cd /tmp/src # Finally, lets run our app! diff --git a/shuffle-ai/1.0.0/api.yaml b/shuffle-ai/1.0.0/api.yaml index a247fad5..2c59a39a 100644 --- a/shuffle-ai/1.0.0/api.yaml +++ b/shuffle-ai/1.0.0/api.yaml @@ -11,7 +11,18 @@ contact_info: url: https://shuffler.io email: support@shuffler.io actions: - - name: autoformat_text + - name: run_llm + description: "Runs a local LLM based on ollama with any of their models from https://github.com/ollama/ollama?tab=readme-ov-file#model-library" + parameters: + - name: question + description: "The input question to the model" + required: true + multiline: true + example: "" + schema: + type: string + + - name: shuffle_cloud_inference description: Input ANY kind of data in the format you want, and the format you want it in. Default is a business-y email. Uses ShuffleGPT, which is based on OpenAI and our own model. parameters: - name: apikey diff --git a/shuffle-ai/1.0.0/requirements.txt b/shuffle-ai/1.0.0/requirements.txt index a783f817..f02fd3c7 100644 --- a/shuffle-ai/1.0.0/requirements.txt +++ b/shuffle-ai/1.0.0/requirements.txt @@ -1,4 +1,6 @@ +shuffle_sdk pytesseract pdf2image pypdf2 requests +ollama diff --git a/shuffle-ai/1.0.0/src/app.py b/shuffle-ai/1.0.0/src/app.py index 3c48d2ee..cc5c8893 100644 --- a/shuffle-ai/1.0.0/src/app.py +++ b/shuffle-ai/1.0.0/src/app.py @@ -1,11 +1,28 @@ import json -import PyPDF2 import tempfile import requests -import pytesseract -from pdf2image import convert_from_path -from walkoff_app_sdk.app_base import AppBase +try: + import pytesseract +except Exception as e: + print("Skipping pytesseract import: %s" % e) + +try: + import PyPDF2 +except Exception as e: + print("Skipping PyPDF2 import: %s" % e) + +try: + from pdf2image import convert_from_path +except Exception as e: + print("Skipping pdf2image import: %s" % e) + +try: + import ollama +except Exception as e: + print("Skipping ollama import: %s" % e) + +from shuffle_sdk import AppBase class Tools(AppBase): __version__ = "1.0.0" @@ -14,6 +31,53 @@ class Tools(AppBase): def __init__(self, redis, logger, console_logger=None): super().__init__(redis, logger, console_logger) + def run_llm(self, question, model="llama3.2"): + models = [] + response = ollama.chat(model=model, messages=[ + { + "role": "user", "content": question, + } + ]) + + return response["message"]["content"] + + def security_assistant(self): + # Currently testing outside the Shuffle environment + # using assistants and local LLMs + + return "Not implemented" + + def shuffle_cloud_inference(self, apikey, text, formatting="auto"): + headers = { + "Authorization": "Bearer %s" % apikey, + } + + if not formatting: + formatting = "auto" + + output_formatting= "Format the following data to be a good email that can be sent to customers. Don't make it too business sounding." + if formatting != "auto": + output_formatting = formatting + + ret = requests.post( + "https://shuffler.io/api/v1/conversation", + json={ + "query": text, + "formatting": output_formatting, + "output_format": "formatting" + }, + headers=headers, + ) + + if ret.status_code != 200: + print(ret.text) + return { + "success": False, + "reason": "Status code for auto-formatter is not 200" + } + + return ret.text + def autoformat_text(self, apikey, text, formatting="auto"): headers = { "Authorization": "Bearer %s" % apikey, From 77156d06e528ab60e02aba373d511683a27ccad1 Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 29 Jan 2025 12:17:46 +0100 Subject: [PATCH 173/259] Rebuild with deepseek install --- shuffle-ai/1.0.0/Dockerfile | 2 +- shuffle-ai/1.0.0/api.yaml | 7 +++++++ shuffle-ai/1.0.0/src/app.py | 4 ++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/shuffle-ai/1.0.0/Dockerfile b/shuffle-ai/1.0.0/Dockerfile index 18920086..d1d8423d 100644 --- a/shuffle-ai/1.0.0/Dockerfile +++ b/shuffle-ai/1.0.0/Dockerfile @@ -65,7 +65,7 @@ RUN ls -alh /usr/bin RUN which ollama #RUN /usr/local/bin/ollama pull llama3.2 -RUN ollama serve & sleep 2 && ollama pull llama3 +RUN ollama serve & sleep 2 && ollama pull nezahatkorkmaz/deepseek-v3 #RUN rm /usr/local/bin/ollama #RUN cd tesseract && ./autogen.sh && ./configure --build=x86_64-alpine-linux-musl --host=x86_64-alpine-linux-musl && make && make install && cd /tmp/src diff --git a/shuffle-ai/1.0.0/api.yaml b/shuffle-ai/1.0.0/api.yaml index 2c59a39a..a834d38a 100644 --- a/shuffle-ai/1.0.0/api.yaml +++ b/shuffle-ai/1.0.0/api.yaml @@ -21,6 +21,13 @@ actions: example: "" schema: type: string + - name: model + description: "The model to run" + required: false + multiline: false + example: "deepseek-v3" + schema: + type: string - name: shuffle_cloud_inference description: Input ANY kind of data in the format you want, and the format you want it in. Default is a business-y email. Uses ShuffleGPT, which is based on OpenAI and our own model. diff --git a/shuffle-ai/1.0.0/src/app.py b/shuffle-ai/1.0.0/src/app.py index cc5c8893..9e81e027 100644 --- a/shuffle-ai/1.0.0/src/app.py +++ b/shuffle-ai/1.0.0/src/app.py @@ -31,8 +31,8 @@ class Tools(AppBase): def __init__(self, redis, logger, console_logger=None): super().__init__(redis, logger, console_logger) - def run_llm(self, question, model="llama3.2"): - models = [] + #def run_llm(self, question, model="llama3.2"): + def run_llm(self, question, model="deepseek-v3"): response = ollama.chat(model=model, messages=[ { "role": "user", "content": question, From ff6cbca7805127919a34ffac4051e10c15e1cf38 Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 29 Jan 2025 13:04:31 +0100 Subject: [PATCH 174/259] Rebuild with newest sdk --- shuffle-ai/1.0.0/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/shuffle-ai/1.0.0/requirements.txt b/shuffle-ai/1.0.0/requirements.txt index f02fd3c7..7a6f6c45 100644 --- a/shuffle-ai/1.0.0/requirements.txt +++ b/shuffle-ai/1.0.0/requirements.txt @@ -4,3 +4,4 @@ pdf2image pypdf2 requests ollama + From 6c890fa143a0b5be20236cf2bff9a0feae2b3737 Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 29 Jan 2025 16:16:13 +0100 Subject: [PATCH 175/259] Build with newer sdk for shuffle-ai --- shuffle-ai/1.0.0/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shuffle-ai/1.0.0/Dockerfile b/shuffle-ai/1.0.0/Dockerfile index d1d8423d..633c0e59 100644 --- a/shuffle-ai/1.0.0/Dockerfile +++ b/shuffle-ai/1.0.0/Dockerfile @@ -1,6 +1,6 @@ FROM python:3.10-slim -# Install all alpine build tools needed for our pip installs +# Install all build tools needed for our pip installs RUN apt update RUN apt install -y clang g++ make automake autoconf libtool cmake From b7f7d94afc9196963ab87bf90297afc169338a79 Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 29 Jan 2025 18:10:46 +0100 Subject: [PATCH 176/259] Rebuild with run_llm working --- shuffle-ai/1.0.0/Dockerfile | 59 +++++++++++++++++-------------- shuffle-ai/1.0.0/requirements.txt | 2 +- shuffle-ai/1.0.0/src/app.py | 51 +++++++++++++++++++++----- 3 files changed, 76 insertions(+), 36 deletions(-) diff --git a/shuffle-ai/1.0.0/Dockerfile b/shuffle-ai/1.0.0/Dockerfile index 633c0e59..8764eaf9 100644 --- a/shuffle-ai/1.0.0/Dockerfile +++ b/shuffle-ai/1.0.0/Dockerfile @@ -1,41 +1,40 @@ FROM python:3.10-slim +### Install Tesseract +ENV SHELL=/bin/bash +ENV CC /usr/bin/clang +ENV CXX /usr/bin/clang++ +ENV LANG=C.UTF-8 +ENV TESSDATA_PREFIX=/usr/local/share/tessdata + # Install all build tools needed for our pip installs RUN apt update RUN apt install -y clang g++ make automake autoconf libtool cmake +## Install the same packages with apt as with apk, but ensure they exist in apt +RUN apt install -y jq git curl +RUN apt install -y file openssl bash tini libpng-dev aspell-en +RUN apt install -y git clang g++ make automake autoconf libtool cmake +RUN apt install -y autoconf-archive wget +RUN mkdir -p /models +RUN wget https://huggingface.co/QuantFactory/Llama-3.2-3B-GGUF/resolve/main/Llama-3.2-3B.Q8_0.gguf?download=true -O /models/Llama-3.2-3B.Q8_0.gguf + # Install all of our pip packages in a single directory that we can copy to our base image later RUN mkdir /install WORKDIR /install # Switch back to our base image and copy in all of our built packages and source code -#COPY --from=builder /install /usr/local -COPY src /app COPY requirements.txt /requirements.txt RUN python3 -m pip install -r /requirements.txt # Install any binary dependencies needed in our final image -# RUN apk --no-cache add --update my_binary_dependency -#RUN apk --no-cache add jq git curl -RUN apt install -y jq git curl -ENV SHELL=/bin/bash - -### Install Tesseract -ENV CC /usr/bin/clang -ENV CXX /usr/bin/clang++ -ENV LANG=C.UTF-8 -ENV TESSDATA_PREFIX=/usr/local/share/tessdata # Dev tools WORKDIR /tmp #RUN apk update #RUN apk upgrade -## Install the same packages with apt as with apk, but ensure they exist in apt -RUN apt install -y file openssl bash tini libpng-dev aspell-en -RUN apt install -y git clang g++ make automake autoconf libtool cmake -RUN apt install -y autoconf-archive wget RUN ln -s /usr/include/locale.h /usr/include/xlocale.h @@ -56,20 +55,26 @@ RUN git clone --depth 1 https://github.com/tesseract-ocr/tesseract.git #RUN curl -fsSL https://ollama.com/install.sh | sh # Install to /usr/local -RUN wget https://ollama.com/install.sh -O /usr/local/bin/ollama-install -RUN chmod +x /usr/local/bin/ollama-install -RUN ls /usr/local/bin -RUN sh /usr/local/bin/ollama-install +#RUN wget https://ollama.com/install.sh -O /usr/local/bin/ollama-install +#RUN chmod +x /usr/local/bin/ollama-install +#RUN sh /usr/local/bin/ollama-install +# +#RUN ls -alh /usr/bin +#RUN ollama serve & sleep 2 && ollama pull nezahatkorkmaz/deepseek-v3 +#CMD ["sh", "-c", "ollama serve & sleep 2 && python app.py --log-level DEBUG"] -RUN ls -alh /usr/bin -RUN which ollama +#RUN wget https://huggingface.co/TheBloke/Mistral-7B-Instruct-v0.1-GGUF/resolve/main/mistral-7b-instruct-v0.1.Q4_K_M.gguf +RUN python3 -m pip install ctransformers --no-binary ctransformers -#RUN /usr/local/bin/ollama pull llama3.2 -RUN ollama serve & sleep 2 && ollama pull nezahatkorkmaz/deepseek-v3 +# Finally, lets run our app! +ENV GIN_MODE=release +ENV SHUFFLE_APP_SDK_TIMEOUT=300 +#ENV LD_LIBRARY_PATH=/usr/local/lib/python3.10/site-packages/ctransformers/lib/basic/libctransformers.so +#RUN chmod 755 /usr/local/lib/python3.10/site-packages/ctransformers/lib/basic/libctransformers.so -#RUN rm /usr/local/bin/ollama -#RUN cd tesseract && ./autogen.sh && ./configure --build=x86_64-alpine-linux-musl --host=x86_64-alpine-linux-musl && make && make install && cd /tmp/src +#RUN apt install -y libffi-dev -# Finally, lets run our app! + +COPY src /app WORKDIR /app CMD ["python", "app.py", "--log-level", "DEBUG"] diff --git a/shuffle-ai/1.0.0/requirements.txt b/shuffle-ai/1.0.0/requirements.txt index 7a6f6c45..94e2d1ce 100644 --- a/shuffle-ai/1.0.0/requirements.txt +++ b/shuffle-ai/1.0.0/requirements.txt @@ -3,5 +3,5 @@ pytesseract pdf2image pypdf2 requests -ollama +llama-cpp-python diff --git a/shuffle-ai/1.0.0/src/app.py b/shuffle-ai/1.0.0/src/app.py index 9e81e027..4e1df3ef 100644 --- a/shuffle-ai/1.0.0/src/app.py +++ b/shuffle-ai/1.0.0/src/app.py @@ -1,3 +1,4 @@ +import os import json import tempfile import requests @@ -18,9 +19,9 @@ print("Skipping pdf2image import: %s" % e) try: - import ollama + import llama_cpp except Exception as e: - print("Skipping ollama import: %s" % e) + print("Skipping llama_cpp import: %s" % e) from shuffle_sdk import AppBase @@ -32,14 +33,48 @@ def __init__(self, redis, logger, console_logger=None): super().__init__(redis, logger, console_logger) #def run_llm(self, question, model="llama3.2"): - def run_llm(self, question, model="deepseek-v3"): - response = ollama.chat(model=model, messages=[ - { - "role": "user", "content": question, + #def run_llm(self, question, model="deepseek-v3"): + def run_llm(self, question, model="/models/Llama-3.2-3B.Q8_0.gguf"): + self.logger.info("[DEBUG] Running LLM with model '%s'" % model) + + if not os.path.exists(model): + return { + "success": False, + "reason": "Model not found at path %s" % model, + "details": "Ensure the model path is correct" } - ]) - return response["message"]["content"] + llm = llama_cpp.Llama(model_path=model) + + # https://github.com/abetlen/llama-cpp-python + output = llm.create_chat_completion( + messages = [ + {"role": "system", "content": "You are an assistant who outputs in JSON format.."}, + { + "role": "user", + "content": question, + } + ] + ) + + return output + + + #model = ctransformers.AutoModelForCausalLM.from_pretrained( + # model_path_or_repo_id=model, + # #model_type="deepseek-v3" + #) + + #resp = model(full_question) + #return resp + + #response = ollama.chat(model=model, messages=[ + # { + # "role": "user", "content": question, + # } + #]) + + #return response["message"]["content"] def security_assistant(self): # Currently testing outside the Shuffle environment From cd5062a9506b6beca1b4297f986d20ece5d3a231 Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 29 Jan 2025 21:02:29 +0100 Subject: [PATCH 177/259] Minor last min fixes --- shuffle-ai/1.0.0/Dockerfile | 12 +++-- shuffle-ai/1.0.0/api.yaml | 8 ++-- shuffle-ai/1.0.0/src/app.py | 92 +++++++++++++++++++++++++++---------- 3 files changed, 80 insertions(+), 32 deletions(-) diff --git a/shuffle-ai/1.0.0/Dockerfile b/shuffle-ai/1.0.0/Dockerfile index 8764eaf9..41711c97 100644 --- a/shuffle-ai/1.0.0/Dockerfile +++ b/shuffle-ai/1.0.0/Dockerfile @@ -17,7 +17,14 @@ RUN apt install -y file openssl bash tini libpng-dev aspell-en RUN apt install -y git clang g++ make automake autoconf libtool cmake RUN apt install -y autoconf-archive wget RUN mkdir -p /models -RUN wget https://huggingface.co/QuantFactory/Llama-3.2-3B-GGUF/resolve/main/Llama-3.2-3B.Q8_0.gguf?download=true -O /models/Llama-3.2-3B.Q8_0.gguf + +# Larger model +RUN wget https://huggingface.co/unsloth/DeepSeek-R1-Distill-Llama-8B-GGUF/resolve/main/DeepSeek-R1-Distill-Llama-8B-Q2_K.gguf +ENV MODEL_PATH="/models/DeepSeek-R1-Distill-Llama-8B-Q2_K.gguf" + +# https://huggingface.co/unsloth/DeepSeek-R1-Distill-Llama-8B-GGUF/resolve/main/DeepSeek-R1-Distill-Llama-8B-Q8_0.gguf +#RUN wget https://huggingface.co/QuantFactory/Llama-3.2-3B-GGUF/resolve/main/Llama-3.2-3B.Q2_K.gguf?download=true -O /models/Llama-3.2-3B.Q8_0.gguf +#RUN wget https://huggingface.co/QuantFactory/Llama-3.2-3B-GGUF/resolve/main/Llama-3.2-3B.Q2_K.gguf?download=true -O /models/Llama-3.2-3B.Q8_0.gguf # Install all of our pip packages in a single directory that we can copy to our base image later RUN mkdir /install @@ -72,9 +79,6 @@ ENV SHUFFLE_APP_SDK_TIMEOUT=300 #ENV LD_LIBRARY_PATH=/usr/local/lib/python3.10/site-packages/ctransformers/lib/basic/libctransformers.so #RUN chmod 755 /usr/local/lib/python3.10/site-packages/ctransformers/lib/basic/libctransformers.so -#RUN apt install -y libffi-dev - - COPY src /app WORKDIR /app CMD ["python", "app.py", "--log-level", "DEBUG"] diff --git a/shuffle-ai/1.0.0/api.yaml b/shuffle-ai/1.0.0/api.yaml index a834d38a..53d08e02 100644 --- a/shuffle-ai/1.0.0/api.yaml +++ b/shuffle-ai/1.0.0/api.yaml @@ -12,7 +12,7 @@ contact_info: email: support@shuffler.io actions: - name: run_llm - description: "Runs a local LLM based on ollama with any of their models from https://github.com/ollama/ollama?tab=readme-ov-file#model-library" + description: "Runs a local LLM, with a GPU or CPU (slow). Default model is set up in Dockerfile" parameters: - name: question description: "The input question to the model" @@ -21,11 +21,11 @@ actions: example: "" schema: type: string - - name: model - description: "The model to run" + - name: system_message + description: "The system message use, if any" required: false multiline: false - example: "deepseek-v3" + example: "" schema: type: string diff --git a/shuffle-ai/1.0.0/src/app.py b/shuffle-ai/1.0.0/src/app.py index 4e1df3ef..0e3990d0 100644 --- a/shuffle-ai/1.0.0/src/app.py +++ b/shuffle-ai/1.0.0/src/app.py @@ -25,6 +25,50 @@ from shuffle_sdk import AppBase +#model = "/models/Llama-3.2-3B.Q8_0.gguf" # Larger +#model = "/models/Llama-3.2-3B.Q2_K.gguf" # Smol + +#model = "/models/DeepSeek-R1-Distill-Llama-8B-Q8_0.gguf" # Larger 8-bit +model = "/models/DeepSeek-R1-Distill-Llama-8B-Q2_K.gguf" # Smaller +if os.getenv("MODEL_PATH"): + model = os.getenv("MODEL_PATH") + +def load_llm_model(model): + if not os.path.exists(model): + model_name = model.split("/")[-1] + # Check $HOME/downloads/{model} + + home_path = os.path.expanduser("~") + print(home_path) + + if os.path.exists(f"{home_path}/downloads/{model_name}"): + model = f"{home_path}/downloads/{model_name}" + else: + return { + "success": False, + "reason": "Model not found at path %s" % model, + "details": "Ensure the model path is correct" + } + + # Check for GPU layers + llm = None + gpu_layers = os.getenv("GPU_LAYERS") + if gpu_layers: + gpu_layers = int(gpu_layers) + if gpu_layers > 0: + print("GPU Layers: %s" % gpu_layers) + llm = llama_cpp.Llama(model_path=model, n_gpu_layers=gpu_layers) + else: + llm = llama_cpp.Llama(model_path=model) + else: + # Check if GPU available + #print("No GPU layers set.") + llm = llama_cpp.Llama(model_path=model) + + return llm + +llm = load_llm_model(model) + class Tools(AppBase): __version__ = "1.0.0" app_name = "Shuffle AI" @@ -34,22 +78,23 @@ def __init__(self, redis, logger, console_logger=None): #def run_llm(self, question, model="llama3.2"): #def run_llm(self, question, model="deepseek-v3"): - def run_llm(self, question, model="/models/Llama-3.2-3B.Q8_0.gguf"): - self.logger.info("[DEBUG] Running LLM with model '%s'" % model) + def run_llm(self, question, system_message=""): + global llm + global model - if not os.path.exists(model): - return { - "success": False, - "reason": "Model not found at path %s" % model, - "details": "Ensure the model path is correct" - } + if not system_message: + system_message = "Be a friendly assistant", - llm = llama_cpp.Llama(model_path=model) + self.logger.info("[DEBUG] Running LLM with model '%s'. To overwrite path, use environment variable MODEL_PATH=" % model) # https://github.com/abetlen/llama-cpp-python output = llm.create_chat_completion( + max_tokens=100, messages = [ - {"role": "system", "content": "You are an assistant who outputs in JSON format.."}, + { + "role": "system", + "content": system_message, + }, { "role": "user", "content": question, @@ -57,24 +102,23 @@ def run_llm(self, question, model="/models/Llama-3.2-3B.Q8_0.gguf"): ] ) - return output - + self.logger.info("[DEBUG] LLM output: %s" % output) - #model = ctransformers.AutoModelForCausalLM.from_pretrained( - # model_path_or_repo_id=model, - # #model_type="deepseek-v3" - #) + new_message = "" + if "choices" in output and len(output["choices"]) > 0: + new_message = output["choices"][0]["message"]["content"] - #resp = model(full_question) - #return resp + parsed_output = { + "success": True, + "model": output["model"], + "tokens": output["tokens"], + "output": new_message, + } - #response = ollama.chat(model=model, messages=[ - # { - # "role": "user", "content": question, - # } - #]) + if not os.getenv("GPU_LAYERS"): + parsed_output["debug"] = "GPU_LAYERS not set. Running on CPU. Set GPU_LAYERS to the number of GPU layers to use (e.g. 8)." - #return response["message"]["content"] + return output def security_assistant(self): # Currently testing outside the Shuffle environment From 294a37d34b9a60ebd67997f7ee8fb55e4e4effd9 Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 29 Jan 2025 21:06:05 +0100 Subject: [PATCH 178/259] Fixed shuffle tools to also use shuffle_sdk instead of old docker container source --- shuffle-tools/1.2.0/requirements.txt | 2 +- shuffle-tools/1.2.0/src/app.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/shuffle-tools/1.2.0/requirements.txt b/shuffle-tools/1.2.0/requirements.txt index b5c8fb09..b3d6161a 100644 --- a/shuffle-tools/1.2.0/requirements.txt +++ b/shuffle-tools/1.2.0/requirements.txt @@ -8,4 +8,4 @@ json2xml==5.0.5 ipaddress==1.0.23 google.auth==2.37.0 paramiko==3.5.0 -shuffle-sdk==0.0.8 +shuffle-sdk==0.0.10 diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 1b69e15a..5d67e56c 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -41,8 +41,8 @@ import concurrent.futures import multiprocessing -from walkoff_app_sdk.app_base import AppBase -#from shuffle_sdk import AppBase +#from walkoff_app_sdk.app_base import AppBase +from shuffle_sdk import AppBase # Override exit(), sys.exit, and os._exit # sys.exit() can be caught, meaning we can have a custom handler for it From c9065698a118c8ec12ef885492585c437732e803 Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 30 Jan 2025 09:56:03 +0100 Subject: [PATCH 179/259] Fixed IOC parser to be WAY faster and handle weird data --- shuffle-tools/1.2.0/src/app.py | 41 ++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 5d67e56c..493cac19 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -2606,14 +2606,39 @@ def run_ssh_command(self, host, port, user_name, private_key_file_id, password, return {"success":"true","output": stdout.read().decode(errors='ignore')} + def cleanup_ioc_data(self, input_data): + # Remove unecessary parts like { and }, quotes etc + input_data = str(input_data) + input_data = input_data.replace("{", "") + input_data = input_data.replace("}", "") + input_data = input_data.replace("\"", "") + input_data = input_data.replace("'", "") + input_data = input_data.replace(" ", "") + input_data = input_data.replace("\t", "") + input_data = input_data.replace("\n", "") + + # Remove html tags + input_data = re.sub(r'<[^>]*>', '', input_data) + + return input_data + + def parse_ioc(self, input_string, input_type="all"): ioc_types = ["domains", "urls", "email_addresses", "ipv4s", "ipv4_cidrs", "md5s", "sha256s", "sha1s", "cves"] + #ioc_types = ["ipv4s"] + + try: + input_string = self.cleanup_ioc_data(input_string) + except Exception as e: + self.logger.info("[ERROR] Failed to cleanup ioc data: %s" % e) # Remember overriding ioc types we care about if input_type == "" or input_type == "all": input_type = "all" else: input_type = input_type.split(",") + + new_input_types = [] for i in range(len(input_type)): item = input_type[i] @@ -2621,11 +2646,23 @@ def parse_ioc(self, input_string, input_type="all"): if not item.endswith("s"): item = "%ss" % item - input_type[i] = item + if item not in ioc_types: + continue + + new_input_types.append(item) - ioc_types = input_type + ioc_types = new_input_types + + # Not used for anything after cleanup fixes + max_size = 7500000 + #if len(input_string) > max_size: + # input_string = input_string[:max_size] + + self.logger.info("[DEBUG] Parsing data of length %d with types %s. Max size: %d" % (len(input_string), ioc_types, max_size)) iocs = find_iocs(str(input_string), included_ioc_types=ioc_types) + self.logger.info("[DEBUG] Found %d iocs" % len(iocs)) + newarray = [] for key, value in iocs.items(): if input_type != "all": From bb6fd14bb5325f3e9fa328c802d9476c10f1c0b4 Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 31 Jan 2025 15:08:27 +0100 Subject: [PATCH 180/259] Added shuffle sdk to email app --- email/1.3.0/requirements.txt | 1 + email/1.3.0/src/app.py | 3 ++- shuffle-tools/1.2.0/requirements.txt | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/email/1.3.0/requirements.txt b/email/1.3.0/requirements.txt index 6ee8846d..659b66ad 100644 --- a/email/1.3.0/requirements.txt +++ b/email/1.3.0/requirements.txt @@ -4,3 +4,4 @@ jsonpickle==2.0.0 eml-parser==2.0.0 msg-parser==1.2.0 +shuffle_sdk==0.0.10 diff --git a/email/1.3.0/src/app.py b/email/1.3.0/src/app.py index 528af89b..73ec0241 100644 --- a/email/1.3.0/src/app.py +++ b/email/1.3.0/src/app.py @@ -19,7 +19,8 @@ from email.mime.text import MIMEText from email.mime.application import MIMEApplication -from walkoff_app_sdk.app_base import AppBase +#from walkoff_app_sdk.app_base import AppBase +from shuffle_sdk import AppBase def json_serial(obj): if isinstance(obj, datetime.datetime): diff --git a/shuffle-tools/1.2.0/requirements.txt b/shuffle-tools/1.2.0/requirements.txt index b3d6161a..3a98348b 100644 --- a/shuffle-tools/1.2.0/requirements.txt +++ b/shuffle-tools/1.2.0/requirements.txt @@ -9,3 +9,4 @@ ipaddress==1.0.23 google.auth==2.37.0 paramiko==3.5.0 shuffle-sdk==0.0.10 + From baaf301e088ded765eab1ea433e66a6a7f979606 Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 31 Jan 2025 15:40:27 +0100 Subject: [PATCH 181/259] Fixed email to have more exception handling --- email/1.3.0/requirements.txt | 2 +- email/1.3.0/src/app.py | 7 ++++++- shuffle-tools/1.2.0/src/app.py | 5 ++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/email/1.3.0/requirements.txt b/email/1.3.0/requirements.txt index 659b66ad..00d2a636 100644 --- a/email/1.3.0/requirements.txt +++ b/email/1.3.0/requirements.txt @@ -1,4 +1,4 @@ -requests==2.25.1 +requests==2.32.3 glom==20.11.0 jsonpickle==2.0.0 diff --git a/email/1.3.0/src/app.py b/email/1.3.0/src/app.py index 73ec0241..08232735 100644 --- a/email/1.3.0/src/app.py +++ b/email/1.3.0/src/app.py @@ -164,11 +164,16 @@ def send_email_smtp(self, smtp_host, recipient, subject, body, smtp_port, attach "success": False, "reason": f"Failed to send mail: {e}" } + except Exception as e: + return { + "success": False, + "reason": f"Failed to send mail (2): {e}" + } self.logger.info("Successfully sent email with subject %s to %s" % (subject, recipient)) return { "success": True, - "reason": "Email sent to %s, %s!" %(recipient,cc_emails) if cc_emails else "Email sent to %s!" % recipient, + "reason": "Email sent to %s, %s!" % (recipient, cc_emails) if cc_emails else "Email sent to %s!" % recipient, "attachments": attachment_count } diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 493cac19..e0b2e335 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -2624,7 +2624,7 @@ def cleanup_ioc_data(self, input_data): def parse_ioc(self, input_string, input_type="all"): - ioc_types = ["domains", "urls", "email_addresses", "ipv4s", "ipv4_cidrs", "md5s", "sha256s", "sha1s", "cves"] + ioc_types = ["domains", "urls", "email_addresses", "ipv4s", "ipv6s", "ipv4_cidrs", "md5s", "sha256s", "sha1s", "cves"] #ioc_types = ["ipv4s"] try: @@ -2652,6 +2652,8 @@ def parse_ioc(self, input_string, input_type="all"): new_input_types.append(item) ioc_types = new_input_types + if len(ioc_types) == 0: + input_type = "all" # Not used for anything after cleanup fixes max_size = 7500000 @@ -2667,6 +2669,7 @@ def parse_ioc(self, input_string, input_type="all"): for key, value in iocs.items(): if input_type != "all": if key not in input_type: + print("Invalid key: %s" % key) continue if len(value) > 0: From 972f2c9147f16114708156c353be02c62552db0b Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 31 Jan 2025 15:46:33 +0100 Subject: [PATCH 182/259] Fixed email app again --- email/1.3.0/src/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/email/1.3.0/src/app.py b/email/1.3.0/src/app.py index 08232735..0b6b707d 100644 --- a/email/1.3.0/src/app.py +++ b/email/1.3.0/src/app.py @@ -114,7 +114,7 @@ def send_email_smtp(self, smtp_host, recipient, subject, body, smtp_port, attach msg["To"] = recipient msg["Subject"] = subject - if cc_emails != None and len(cc_emails) > 0: + if cc_emails: msg["Cc"] = cc_emails self.logger.info("Pre mime check") @@ -156,7 +156,7 @@ def send_email_smtp(self, smtp_host, recipient, subject, body, smtp_port, attach except Exception as e: self.logger.info(f"Error in attachment parsing for email: {e}") - self.logger.info("Pre send msg") + self.logger.info(f"Pre send msg: {msg}") try: s.send_message(msg) except smtplib.SMTPDataError as e: From 7b6ec86293b7387fe9aa003c2c3aff4e0afe7390 Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 31 Jan 2025 15:50:08 +0100 Subject: [PATCH 183/259] From field fix --- email/1.3.0/src/app.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/email/1.3.0/src/app.py b/email/1.3.0/src/app.py index 0b6b707d..5f0f416f 100644 --- a/email/1.3.0/src/app.py +++ b/email/1.3.0/src/app.py @@ -99,10 +99,13 @@ def send_email_smtp(self, smtp_host, recipient, subject, body, smtp_port, attach try: s.login(username, password) except smtplib.SMTPAuthenticationError as e: - return { - "success": False, - "reason": f"Bad username or password: {e}" - } + if len(password) == 0: + self.logger.info("No password provided. Continuing as auth may not be necessary.") + else: + return { + "success": False, + "reason": f"Bad username or password: {e}" + } if body_type == "" or len(body_type) < 3: body_type = "html" From 26e14a2d558817860ad099b5652f8d2f23dae308 Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 31 Jan 2025 15:51:57 +0100 Subject: [PATCH 184/259] More fixes --- email/1.3.0/src/app.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/email/1.3.0/src/app.py b/email/1.3.0/src/app.py index 5f0f416f..64f6e167 100644 --- a/email/1.3.0/src/app.py +++ b/email/1.3.0/src/app.py @@ -98,9 +98,18 @@ def send_email_smtp(self, smtp_host, recipient, subject, body, smtp_port, attach if len(username) > 1 or len(password) > 1: try: s.login(username, password) + except Exception as e: + if len(password) == 0: + self.logger.info("[WARNING] Auth failed (2). No password provided. Continuing as auth may not be necessary.") + else: + return { + "success": False, + "reason": f"General auth exception: {e}" + } + except smtplib.SMTPAuthenticationError as e: if len(password) == 0: - self.logger.info("No password provided. Continuing as auth may not be necessary.") + self.logger.info("[WARNING] Auth failed. No password provided. Continuing as auth may not be necessary.") else: return { "success": False, From 299e1f37f2a89a81a031c73e6132b3a5dbe41dea Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 31 Jan 2025 15:57:36 +0100 Subject: [PATCH 185/259] Fixed email sender problem. Username now required. --- email/1.3.0/api.yaml | 2 +- email/1.3.0/src/app.py | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/email/1.3.0/api.yaml b/email/1.3.0/api.yaml index eca1cf76..2ec0fdfe 100644 --- a/email/1.3.0/api.yaml +++ b/email/1.3.0/api.yaml @@ -52,7 +52,7 @@ actions: description: The SMTP login username multiline: false example: "support@shuffler.io" - required: false + required: true schema: type: string - name: password diff --git a/email/1.3.0/src/app.py b/email/1.3.0/src/app.py index 64f6e167..7b8befbd 100644 --- a/email/1.3.0/src/app.py +++ b/email/1.3.0/src/app.py @@ -104,7 +104,7 @@ def send_email_smtp(self, smtp_host, recipient, subject, body, smtp_port, attach else: return { "success": False, - "reason": f"General auth exception: {e}" + "reason": f"General login exception: {e}" } except smtplib.SMTPAuthenticationError as e: @@ -123,6 +123,12 @@ def send_email_smtp(self, smtp_host, recipient, subject, body, smtp_port, attach self.logger.info("Pre mime multipart") msg = MIMEMultipart() msg["From"] = username + if len(username) == 0: + return { + "success": False, + "reason": "No username provided (sender). Please provide a username." + } + msg["To"] = recipient msg["Subject"] = subject From 81f7eaaca0790e25db381f484d798483a8c470f0 Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 31 Jan 2025 16:22:22 +0100 Subject: [PATCH 186/259] Improved capture of IOCs --- email/1.3.0/src/app.py | 2 +- shuffle-tools/1.2.0/src/app.py | 52 +++++++++++++++++++--------------- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/email/1.3.0/src/app.py b/email/1.3.0/src/app.py index 7b8befbd..de5acdcc 100644 --- a/email/1.3.0/src/app.py +++ b/email/1.3.0/src/app.py @@ -126,7 +126,7 @@ def send_email_smtp(self, smtp_host, recipient, subject, body, smtp_port, attach if len(username) == 0: return { "success": False, - "reason": "No username provided (sender). Please provide a username." + "reason": "No username provided (sender). Please provide a username. Required since January 2025." } msg["To"] = recipient diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index e0b2e335..2087998c 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -2613,9 +2613,10 @@ def cleanup_ioc_data(self, input_data): input_data = input_data.replace("}", "") input_data = input_data.replace("\"", "") input_data = input_data.replace("'", "") - input_data = input_data.replace(" ", "") - input_data = input_data.replace("\t", "") - input_data = input_data.replace("\n", "") + + input_data = input_data.replace("\t", " ") + input_data = input_data.replace(" ", " ") + input_data = input_data.replace("\n\n", "\n") # Remove html tags input_data = re.sub(r'<[^>]*>', '', input_data) @@ -2661,9 +2662,11 @@ def parse_ioc(self, input_string, input_type="all"): # input_string = input_string[:max_size] self.logger.info("[DEBUG] Parsing data of length %d with types %s. Max size: %d" % (len(input_string), ioc_types, max_size)) + self.logger.info(f"STRING: {input_string}") - iocs = find_iocs(str(input_string), included_ioc_types=ioc_types) - self.logger.info("[DEBUG] Found %d iocs" % len(iocs)) + #iocs = find_iocs(str(input_string), included_ioc_types=ioc_types) + iocs = find_iocs(str(input_string)) + self.logger.info("[DEBUG] Found %d ioc types" % len(iocs)) newarray = [] for key, value in iocs.items(): @@ -2672,24 +2675,27 @@ def parse_ioc(self, input_string, input_type="all"): print("Invalid key: %s" % key) continue - if len(value) > 0: - for item in value: - # If in here: attack techniques. Shouldn't be 3 levels so no - # recursion necessary - if isinstance(value, dict): - for subkey, subvalue in value.items(): - if len(subvalue) > 0: - for subitem in subvalue: - data = { - "data": subitem, - "data_type": "%s_%s" % (key[:-1], subkey), - } - if data not in newarray: - newarray.append(data) - else: - data = {"data": item, "data_type": key[:-1]} - if data not in newarray: - newarray.append(data) + print(key, value) + if len(value) == 0: + continue + + for item in value: + # If in here: attack techniques. Shouldn't be 3 levels so no + # recursion necessary + if isinstance(value, dict): + for subkey, subvalue in value.items(): + if len(subvalue) > 0: + for subitem in subvalue: + data = { + "data": subitem, + "data_type": "%s_%s" % (key[:-1], subkey), + } + if data not in newarray: + newarray.append(data) + else: + data = {"data": item, "data_type": key[:-1]} + if data not in newarray: + newarray.append(data) # Reformatting IP for item in newarray: From fd7b1582de49ed016627164fb4b53bfa0023cca8 Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 3 Feb 2025 16:50:06 +0100 Subject: [PATCH 187/259] Retagging of shuffle AI --- shuffle-ai/1.0.0/api.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/shuffle-ai/1.0.0/api.yaml b/shuffle-ai/1.0.0/api.yaml index 53d08e02..734d5203 100644 --- a/shuffle-ai/1.0.0/api.yaml +++ b/shuffle-ai/1.0.0/api.yaml @@ -3,8 +3,12 @@ app_version: 1.0.0 name: Shuffle AI description: An EXPERIMENTAL AI tool app for Shuffle tags: + - AI - Shuffle + - LLM categories: + - AI + - LLM - Shuffle contact_info: name: "@frikkylikeme" From 47cd96edd7b7cd8d437c0c5b955a5164e2be2a50 Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 3 Feb 2025 20:57:39 +0100 Subject: [PATCH 188/259] Minor changes to the run_llm example --- shuffle-ai/1.0.0/api.yaml | 2 +- shuffle-ai/1.0.0/src/app.py | 4 ++-- shuffle-ai/1.0.0/upload.sh | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/shuffle-ai/1.0.0/api.yaml b/shuffle-ai/1.0.0/api.yaml index 734d5203..8259891d 100644 --- a/shuffle-ai/1.0.0/api.yaml +++ b/shuffle-ai/1.0.0/api.yaml @@ -18,7 +18,7 @@ actions: - name: run_llm description: "Runs a local LLM, with a GPU or CPU (slow). Default model is set up in Dockerfile" parameters: - - name: question + - name: input description: "The input question to the model" required: true multiline: true diff --git a/shuffle-ai/1.0.0/src/app.py b/shuffle-ai/1.0.0/src/app.py index 0e3990d0..0bc5b320 100644 --- a/shuffle-ai/1.0.0/src/app.py +++ b/shuffle-ai/1.0.0/src/app.py @@ -78,7 +78,7 @@ def __init__(self, redis, logger, console_logger=None): #def run_llm(self, question, model="llama3.2"): #def run_llm(self, question, model="deepseek-v3"): - def run_llm(self, question, system_message=""): + def run_llm(self, input, system_message=""): global llm global model @@ -97,7 +97,7 @@ def run_llm(self, question, system_message=""): }, { "role": "user", - "content": question, + "content": input, } ] ) diff --git a/shuffle-ai/1.0.0/upload.sh b/shuffle-ai/1.0.0/upload.sh index e5a55fb5..2cfe782b 100755 --- a/shuffle-ai/1.0.0/upload.sh +++ b/shuffle-ai/1.0.0/upload.sh @@ -2,5 +2,6 @@ gcloud run deploy shuffle-ai-1-0-0 \ --region=europe-west2 \ --max-instances=5 \ + --memory=2Gi \ --set-env-vars=SHUFFLE_APP_EXPOSED_PORT=8080,SHUFFLE_SWARM_CONFIG=run,SHUFFLE_LOGS_DISABLED=true,SHUFFLE_APP_SDK_TIMEOUT=120 --source=./ \ --timeout=120s From 862a4dd236137abd25d3adb5413c8b51a00be711 Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 5 Feb 2025 10:08:01 +0100 Subject: [PATCH 189/259] Updated generate random string function --- shuffle-ai/1.0.0/src/app.py | 46 +++++++++++++++++++--------------- shuffle-tools/1.2.0/src/app.py | 2 +- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/shuffle-ai/1.0.0/src/app.py b/shuffle-ai/1.0.0/src/app.py index 0bc5b320..5dd358de 100644 --- a/shuffle-ai/1.0.0/src/app.py +++ b/shuffle-ai/1.0.0/src/app.py @@ -51,21 +51,21 @@ def load_llm_model(model): } # Check for GPU layers - llm = None + innerllm = None gpu_layers = os.getenv("GPU_LAYERS") if gpu_layers: gpu_layers = int(gpu_layers) if gpu_layers > 0: print("GPU Layers: %s" % gpu_layers) - llm = llama_cpp.Llama(model_path=model, n_gpu_layers=gpu_layers) + innerllm = llama_cpp.Llama(model_path=model, n_gpu_layers=gpu_layers) else: - llm = llama_cpp.Llama(model_path=model) + innerllm = llama_cpp.Llama(model_path=model) else: # Check if GPU available #print("No GPU layers set.") - llm = llama_cpp.Llama(model_path=model) + innerllm = llama_cpp.Llama(model_path=model) - return llm + return innerllm llm = load_llm_model(model) @@ -76,8 +76,6 @@ class Tools(AppBase): def __init__(self, redis, logger, console_logger=None): super().__init__(redis, logger, console_logger) - #def run_llm(self, question, model="llama3.2"): - #def run_llm(self, question, model="deepseek-v3"): def run_llm(self, input, system_message=""): global llm global model @@ -88,19 +86,27 @@ def run_llm(self, input, system_message=""): self.logger.info("[DEBUG] Running LLM with model '%s'. To overwrite path, use environment variable MODEL_PATH=" % model) # https://github.com/abetlen/llama-cpp-python - output = llm.create_chat_completion( - max_tokens=100, - messages = [ - { - "role": "system", - "content": system_message, - }, - { - "role": "user", - "content": input, - } - ] - ) + try: + self.logger.info("[DEBUG] LLM: %s" % llm) + output = llm.create_chat_completion( + max_tokens=100, + messages = [ + { + "role": "system", + "content": system_message, + }, + { + "role": "user", + "content": input, + } + ] + ) + except Exception as e: + return { + "success": False, + "reason": f"Failed to run local LLM. Check logs in this execution for more info: {self.current_execution_id}", + "details": f"{e}" + } self.logger.info("[DEBUG] LLM output: %s" % output) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 2087998c..c774e8e0 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -2546,7 +2546,7 @@ def get_standardized_data(self, json_input, input_type): "changed_fields": important_fields, } - def generate_random_string(length=16, special_characters=True): + def generate_random_string(self, length=16, special_characters=True): try: length = int(length) except: From 8f819c2019ddcc33f7168c54ff036f24e996967b Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 10 Feb 2025 10:57:59 +0100 Subject: [PATCH 190/259] Shuffle SDK bumps --- email/1.3.0/requirements.txt | 2 +- shuffle-ai/1.0.0/requirements.txt | 2 +- shuffle-tools/1.2.0/requirements.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/email/1.3.0/requirements.txt b/email/1.3.0/requirements.txt index 00d2a636..90d0c96d 100644 --- a/email/1.3.0/requirements.txt +++ b/email/1.3.0/requirements.txt @@ -4,4 +4,4 @@ jsonpickle==2.0.0 eml-parser==2.0.0 msg-parser==1.2.0 -shuffle_sdk==0.0.10 +shuffle_sdk==0.0.11 diff --git a/shuffle-ai/1.0.0/requirements.txt b/shuffle-ai/1.0.0/requirements.txt index 94e2d1ce..a0a284d0 100644 --- a/shuffle-ai/1.0.0/requirements.txt +++ b/shuffle-ai/1.0.0/requirements.txt @@ -1,4 +1,4 @@ -shuffle_sdk +shuffle_sdk==0.0.11 pytesseract pdf2image pypdf2 diff --git a/shuffle-tools/1.2.0/requirements.txt b/shuffle-tools/1.2.0/requirements.txt index 3a98348b..8cb79fe0 100644 --- a/shuffle-tools/1.2.0/requirements.txt +++ b/shuffle-tools/1.2.0/requirements.txt @@ -8,5 +8,5 @@ json2xml==5.0.5 ipaddress==1.0.23 google.auth==2.37.0 paramiko==3.5.0 -shuffle-sdk==0.0.10 +shuffle-sdk==0.0.11 From 5467e3b3b14196fc604ee62f7a49568a362405a2 Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 20 Feb 2025 00:34:31 +0100 Subject: [PATCH 191/259] Added a new test mechanism for shuffle tools --- email/1.3.0/src/app.py | 3 ++ http/1.4.0/api.yaml | 15 ++++++- shuffle-ai/1.0.0/Dockerfile | 34 ++++++++++++--- shuffle-ai/1.0.0/docker-compose.yml | 20 --------- shuffle-ai/1.0.0/src/app.py | 67 ++++++++++++++++++++++++----- shuffle-ai/1.0.0/upload.sh | 19 +++++--- shuffle-tools/1.2.0/src/app.py | 3 ++ yara/1.0.0/upload.sh | 1 + 8 files changed, 119 insertions(+), 43 deletions(-) delete mode 100644 shuffle-ai/1.0.0/docker-compose.yml diff --git a/email/1.3.0/src/app.py b/email/1.3.0/src/app.py index de5acdcc..a233ce09 100644 --- a/email/1.3.0/src/app.py +++ b/email/1.3.0/src/app.py @@ -427,6 +427,9 @@ def remove_similar_items(self, items): return result def parse_eml(self, filedata, extract_attachments=False): + if filedata.startswith("file_"): + return self.parse_email_file(filedata, extract_attachments) + parsedfile = { "success": True, "filename": "email.eml", diff --git a/http/1.4.0/api.yaml b/http/1.4.0/api.yaml index cad10c2c..0c542673 100644 --- a/http/1.4.0/api.yaml +++ b/http/1.4.0/api.yaml @@ -88,7 +88,20 @@ actions: returns: schema: type: string - example: "404 NOT FOUND" + example: | + { + "status": 200, + "body": { + "example": "json" + "data": "json" + }, + "url": "https://example.com", + "headers": { + "Content-Type": "application/json" + }, + "cookies": {}, + "success": true + } - name: POST description: Runs a POST request towards the specified endpoint parameters: diff --git a/shuffle-ai/1.0.0/Dockerfile b/shuffle-ai/1.0.0/Dockerfile index 41711c97..319b55f1 100644 --- a/shuffle-ai/1.0.0/Dockerfile +++ b/shuffle-ai/1.0.0/Dockerfile @@ -15,16 +15,34 @@ RUN apt install -y clang g++ make automake autoconf libtool cmake RUN apt install -y jq git curl RUN apt install -y file openssl bash tini libpng-dev aspell-en RUN apt install -y git clang g++ make automake autoconf libtool cmake -RUN apt install -y autoconf-archive wget -RUN mkdir -p /models +RUN apt install -y autoconf-archive wget + +# Install cuda toolkit +#RUN wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/cuda-archive-keyring.gpg +#RUN dpkg -i cuda-archive-keyring.gpg +#RUN rm cuda-archive-keyring.gpg +#RUN apt update +#RUN apt install -y cuda +#RUN echo 'export PATH=/usr/local/cuda/bin:$PATH' >> ~/.bashrc +#RUN echo 'export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH' >> ~/.bashrc +#RUN source ~/.bashrc # Larger model -RUN wget https://huggingface.co/unsloth/DeepSeek-R1-Distill-Llama-8B-GGUF/resolve/main/DeepSeek-R1-Distill-Llama-8B-Q2_K.gguf -ENV MODEL_PATH="/models/DeepSeek-R1-Distill-Llama-8B-Q2_K.gguf" +RUN mkdir -p /models + +# Fails. 6 bit, 8B model. +#RUN wget https://huggingface.co/RichardErkhov/meta-llama_-_Meta-Llama-3-8B-gguf/blob/main/Meta-Llama-3-8B.Q6_K.gguf?download=true -O /models/Meta-Llama-3-8B.Q6_K.gguf +#ENV MODEL_PATH="/models/Meta-Llama-3-8B.Q6_K.gguf" + +# Simple small Llama wrapper +RUN wget https://huggingface.co/unsloth/DeepSeek-R1-Distill-Llama-8B-GGUF/resolve/main/DeepSeek-R1-Distill-Llama-8B-Q2_K.gguf?download=true -O /models/DeepSeek-R1-Distill-Llama.gguf +# Larger one +#RUN wget https://huggingface.co/unsloth/DeepSeek-R1-Distill-Llama-8B-GGUF/resolve/main/DeepSeek-R1-Distill-Llama-8B-Q8_0.gguf?download=true -O /models/DeepSeek-R1-Distill-Llama.gguf +ENV MODEL_PATH="/models/DeepSeek-R1-Distill-Llama.gguf" + +# Failing? Bad magic bytes. +#RUN wget https://huggingface.co/QuantFactory/Llama-3.2-3B-GGUF/resolve/main/Llama-3.2-3B.Q2_K.gguf?download=true -O /models/Llama-3.2-3B.Q2_K.gguf -# https://huggingface.co/unsloth/DeepSeek-R1-Distill-Llama-8B-GGUF/resolve/main/DeepSeek-R1-Distill-Llama-8B-Q8_0.gguf -#RUN wget https://huggingface.co/QuantFactory/Llama-3.2-3B-GGUF/resolve/main/Llama-3.2-3B.Q2_K.gguf?download=true -O /models/Llama-3.2-3B.Q8_0.gguf -#RUN wget https://huggingface.co/QuantFactory/Llama-3.2-3B-GGUF/resolve/main/Llama-3.2-3B.Q2_K.gguf?download=true -O /models/Llama-3.2-3B.Q8_0.gguf # Install all of our pip packages in a single directory that we can copy to our base image later RUN mkdir /install @@ -33,6 +51,8 @@ WORKDIR /install # Switch back to our base image and copy in all of our built packages and source code COPY requirements.txt /requirements.txt RUN python3 -m pip install -r /requirements.txt +RUN CMAKE_ARGS="-DLLAMA_CUBLAS=on" python3 -m pip install llama-cpp-python --upgrade --force-reinstall --no-cache-dir + # Install any binary dependencies needed in our final image diff --git a/shuffle-ai/1.0.0/docker-compose.yml b/shuffle-ai/1.0.0/docker-compose.yml deleted file mode 100644 index 40ee05f6..00000000 --- a/shuffle-ai/1.0.0/docker-compose.yml +++ /dev/null @@ -1,20 +0,0 @@ -version: '3.4' -services: - hello_world: - build: - context: . - dockerfile: Dockerfile -# image: walkoff_registry:5000/walkoff_app_HelloWorld-v1-0 - deploy: - mode: replicated - replicas: 10 - restart_policy: - condition: none - restart: "no" - secrets: - - secret1 -secrets: - secret1: - file: ./secret_data - labels: - foo: bar diff --git a/shuffle-ai/1.0.0/src/app.py b/shuffle-ai/1.0.0/src/app.py index 5dd358de..f66e01fd 100644 --- a/shuffle-ai/1.0.0/src/app.py +++ b/shuffle-ai/1.0.0/src/app.py @@ -18,23 +18,29 @@ except Exception as e: print("Skipping pdf2image import: %s" % e) + try: import llama_cpp except Exception as e: print("Skipping llama_cpp import: %s" % e) +print("LD Library: '%s'" % os.environ.get("LD_LIBRARY_PATH", "")) + from shuffle_sdk import AppBase #model = "/models/Llama-3.2-3B.Q8_0.gguf" # Larger #model = "/models/Llama-3.2-3B.Q2_K.gguf" # Smol -#model = "/models/DeepSeek-R1-Distill-Llama-8B-Q8_0.gguf" # Larger 8-bit -model = "/models/DeepSeek-R1-Distill-Llama-8B-Q2_K.gguf" # Smaller +#model = "/models/DeepSeek-R1-Distill-Llama-8B-Q2_K.gguf" # Smaller +#model = "/models/Meta-Llama-3-8B.Q6_K.gguf" +model = "/models/DeepSeek-R1-Distill-Llama.gguf" if os.getenv("MODEL_PATH"): model = os.getenv("MODEL_PATH") def load_llm_model(model): + print("Using model path '%s'" % model) if not os.path.exists(model): + print("Could not find model at path %s" % model) model_name = model.split("/")[-1] # Check $HOME/downloads/{model} @@ -54,20 +60,34 @@ def load_llm_model(model): innerllm = None gpu_layers = os.getenv("GPU_LAYERS") if gpu_layers: + print("GPU Layers: %s" % gpu_layers) + gpu_layers = int(gpu_layers) if gpu_layers > 0: - print("GPU Layers: %s" % gpu_layers) innerllm = llama_cpp.Llama(model_path=model, n_gpu_layers=gpu_layers) else: - innerllm = llama_cpp.Llama(model_path=model) + innerllm = llama_cpp.Llama(model_path=model, n_gpu_layers=8) else: # Check if GPU available - #print("No GPU layers set.") - innerllm = llama_cpp.Llama(model_path=model) + print("No GPU layers set.") + #innerllm = llama_cpp.Llama(model_path=model) + + return { + "success": False, + "reason": "GPU layers not set", + "details": "Set GPU_LAYERS environment variable to the number of GPU layers to use (e.g. 8)." + } return innerllm -llm = load_llm_model(model) +try: + llm = load_llm_model(model) +except Exception as e: + print("[ERROR] Failed to load LLM model: %s" % e) + llm = { + "success": False, + "reason": "Failed to load LLM model %s" % model, + } class Tools(AppBase): __version__ = "1.0.0" @@ -80,13 +100,35 @@ def run_llm(self, input, system_message=""): global llm global model + self.logger.info("[DEBUG] LD LIbrary: '%s'. If this is empty, GPU's may not work." % os.environ.get("LD_LIBRARY_PATH", "")) + if not system_message: - system_message = "Be a friendly assistant", + system_message = "Answer their question directly. Don't use HTML or Markdown", self.logger.info("[DEBUG] Running LLM with model '%s'. To overwrite path, use environment variable MODEL_PATH=" % model) + # Check if llm is a dict or not and look for success and reason in it + if not llm: + return { + "success": False, + "reason": "LLM model not loaded", + "details": "Ensure the LLM model is loaded", + "gpu_layers": os.getenv("GPU_LAYERS"), + } + + if isinstance(llm, dict): + if "success" in llm and not llm["success"]: + # List files in /model folder + llm["folder"] = os.listdir("/models") + llm["gpu_layers"] = os.getenv("GPU_LAYERS") + return llm + + self.logger.info("[DEBUG] Running LLM with input '%s' and system message '%s'. GPU Layers: %s" % (input, system_message, os.getenv("GPU_LAYERS"))) + # https://github.com/abetlen/llama-cpp-python try: + print("LLM: ", llm) + self.logger.info("[DEBUG] LLM: %s" % llm) output = llm.create_chat_completion( max_tokens=100, @@ -117,14 +159,19 @@ def run_llm(self, input, system_message=""): parsed_output = { "success": True, "model": output["model"], - "tokens": output["tokens"], "output": new_message, } + if "tokens" in output: + parsed_output["tokens"] = output["tokens"] + + if "usage" in output: + parsed_output["tokens"] = output["usage"] + if not os.getenv("GPU_LAYERS"): parsed_output["debug"] = "GPU_LAYERS not set. Running on CPU. Set GPU_LAYERS to the number of GPU layers to use (e.g. 8)." - return output + return parsed_output def security_assistant(self): # Currently testing outside the Shuffle environment diff --git a/shuffle-ai/1.0.0/upload.sh b/shuffle-ai/1.0.0/upload.sh index 2cfe782b..b449aa4b 100755 --- a/shuffle-ai/1.0.0/upload.sh +++ b/shuffle-ai/1.0.0/upload.sh @@ -1,7 +1,16 @@ +gcloud config set project shuffler -gcloud run deploy shuffle-ai-1-0-0 \ - --region=europe-west2 \ - --max-instances=5 \ - --memory=2Gi \ - --set-env-vars=SHUFFLE_APP_EXPOSED_PORT=8080,SHUFFLE_SWARM_CONFIG=run,SHUFFLE_LOGS_DISABLED=true,SHUFFLE_APP_SDK_TIMEOUT=120 --source=./ \ +gcloud beta run deploy shuffle-ai-1-0-0 \ + --project=shuffler \ + --region=europe-west4 \ + --source=./ \ + --max-instances=1 \ + --concurrency=64 \ + --gpu 1 --gpu-type=nvidia-l4 \ + --cpu 4 \ + --memory=16Gi \ + --no-cpu-throttling \ + --set-env-vars=MODEL_PATH=/models/DeepSeek-R1-Distill-Llama.gguf,GPU_LAYERS=64,SHUFFLE_APP_EXPOSED_PORT=8080,SHUFFLE_SWARM_CONFIG=run,SHUFFLE_LOGS_DISABLED=true,SHUFFLE_APP_SDK_TIMEOUT=300,LD_LIBRARY_PATH=/usr/local/lib:/usr/local/nvidia/lib64:$LD_LIBRARY_PATH \ + --source=./ \ + --service-account=shuffle-apps@shuffler.iam.gserviceaccount.com \ --timeout=120s diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index c774e8e0..19ccfd23 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -2931,6 +2931,9 @@ def merge_incoming_branches(self, input_type="list"): return results + def bodyparse_test(self, body): + return body + def list_cidr_ips(self, cidr): defaultreturn = { "success": False, diff --git a/yara/1.0.0/upload.sh b/yara/1.0.0/upload.sh index f59fd283..e098fa32 100644 --- a/yara/1.0.0/upload.sh +++ b/yara/1.0.0/upload.sh @@ -26,6 +26,7 @@ gcloud run deploy yara \ --region=europe-west2 \ --max-instances=1 \ --set-env-vars=SHUFFLE_APP_EXPOSED_PORT=8080,SHUFFLE_SWARM_CONFIG=run,SHUFFLE_LOGS_DISABLED=true --source=./ \ + --service-account=shuffle-apps@shuffler.iam.gserviceaccount.com \ --timeout=1800s # With image From 485f08a26ac5be003d5953ea3299fa844db5a25a Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 24 Feb 2025 11:49:25 +0100 Subject: [PATCH 192/259] Fixed something for SSL --- active-directory/1.0.0/src/app.py | 2 +- shuffle-tools/1.2.0/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/active-directory/1.0.0/src/app.py b/active-directory/1.0.0/src/app.py index 44d4438b..e9a3558f 100644 --- a/active-directory/1.0.0/src/app.py +++ b/active-directory/1.0.0/src/app.py @@ -27,7 +27,7 @@ def __init__(self, redis, logger, console_logger=None): super().__init__(redis, logger, console_logger) def __ldap_connection(self, server, port, domain, login_user, password, use_ssl): - use_SSL = False if use_ssl.lower() == "false" else False + use_SSL = False if use_ssl.lower() == "false" else True login_dn = domain + "\\" + login_user s = Server(server, port=int(port), use_ssl=use_SSL) diff --git a/shuffle-tools/1.2.0/requirements.txt b/shuffle-tools/1.2.0/requirements.txt index 8cb79fe0..72105f7e 100644 --- a/shuffle-tools/1.2.0/requirements.txt +++ b/shuffle-tools/1.2.0/requirements.txt @@ -8,5 +8,5 @@ json2xml==5.0.5 ipaddress==1.0.23 google.auth==2.37.0 paramiko==3.5.0 -shuffle-sdk==0.0.11 +shuffle-sdk==0.0.12 From 9666d707a3e39088afdf33d977a2e7cd254a2fdd Mon Sep 17 00:00:00 2001 From: Frikky Date: Tue, 25 Feb 2025 02:30:21 +0100 Subject: [PATCH 193/259] Made datastore keys have category --- shuffle-tools/1.2.0/api.yaml | 28 ++++++++++++++ shuffle-tools/1.2.0/requirements.txt | 2 +- shuffle-tools/1.2.0/src/app.py | 55 ++++++++++++++++++++++------ 3 files changed, 72 insertions(+), 13 deletions(-) diff --git a/shuffle-tools/1.2.0/api.yaml b/shuffle-tools/1.2.0/api.yaml index ff6162b6..5867a19c 100644 --- a/shuffle-tools/1.2.0/api.yaml +++ b/shuffle-tools/1.2.0/api.yaml @@ -111,6 +111,13 @@ actions: example: "timestamp" schema: type: string + - name: category + description: The category to get the value from. Not required. + required: false + multiline: false + example: "tickets" + schema: + type: string - name: get_cache_value description: Get a value saved to your organization in Shuffle parameters: @@ -121,6 +128,13 @@ actions: example: "timestamp" schema: type: string + - name: category + description: The category to get the value from. Not required. + required: false + multiline: false + example: "tickets" + schema: + type: string returns: schema: type: string @@ -141,6 +155,13 @@ actions: example: "1621959545" schema: type: string + - name: category + description: The category to get the value from. Not required. + required: false + multiline: false + example: "tickets" + schema: + type: string returns: schema: type: string @@ -154,6 +175,13 @@ actions: example: "timestamp" schema: type: string + - name: category + description: The category to get the value from. Not required. + required: false + multiline: false + example: "tickets" + schema: + type: string returns: schema: type: string diff --git a/shuffle-tools/1.2.0/requirements.txt b/shuffle-tools/1.2.0/requirements.txt index 72105f7e..07e5bca1 100644 --- a/shuffle-tools/1.2.0/requirements.txt +++ b/shuffle-tools/1.2.0/requirements.txt @@ -8,5 +8,5 @@ json2xml==5.0.5 ipaddress==1.0.23 google.auth==2.37.0 paramiko==3.5.0 -shuffle-sdk==0.0.12 +shuffle-sdk==0.0.13 diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 19ccfd23..40892f0d 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -1826,7 +1826,7 @@ def escape_html(self, input_data): result = markupsafe.escape(mapping) return mapping - def check_cache_contains(self, key, value, append): + def check_cache_contains(self, key, value, append, category=""): org_id = self.full_execution["workflow"]["execution_org"]["id"] url = "%s/api/v1/orgs/%s/get_cache" % (self.url, org_id) data = { @@ -1837,8 +1837,11 @@ def check_cache_contains(self, key, value, append): "search": str(value), "key": key, } - directcall = False + if category: + data["category"] = category + + directcall = False allvalues = {} try: for item in self.local_storage: @@ -1879,6 +1882,9 @@ def check_cache_contains(self, key, value, append): allvalues = self.shared_cache if "success" not in allvalues: + if category: + data["category"] = category + get_response = requests.post(url, json=data, verify=False) allvalues = get_response.json() directcall = True @@ -1893,22 +1899,28 @@ def check_cache_contains(self, key, value, append): if append == True: new_value = [str(value)] data["value"] = json.dumps(new_value) + if category: + data["category"] = category set_url = "%s/api/v1/orgs/%s/set_cache" % (self.url, org_id) set_response = requests.post(set_url, json=data, verify=False) try: allvalues = set_response.json() self.shared_cache = self.preload_cache(key=key) - #allvalues["key"] = key - #return allvalues + + newvalue = data["value"] + try: + newvalue = json.loads(data["value"]) + except json.JSONDecodeError: + pass return { "success": True, "found": False, "key": key, "search": value, - "value": new_value, + "value": newvalue, } except Exception as e: return { @@ -2000,6 +2012,7 @@ def check_cache_contains(self, key, value, append): if value not in allvalues["value"] and isinstance(allvalues["value"], list): self.cache_update_buffer.append(value) allvalues["value"].append(value) + exception = "" try: # FIXME: This is a hack, but it works @@ -2009,12 +2022,18 @@ def check_cache_contains(self, key, value, append): new_value = [value] data["value"] = json.dumps(new_value) + if category: + data["category"] = category set_url = "%s/api/v1/orgs/%s/set_cache" % (self.url, org_id) response = requests.post(set_url, json=data, verify=False) allvalues = response.json() - #return allvalues + newvalue = data["value"] + try: + newvalue = json.loads(data["value"]) + except: + pass return { "success": True, @@ -2022,7 +2041,7 @@ def check_cache_contains(self, key, value, append): "reason": f"Appended as it didn't exist", "key": key, "search": value, - "value": data["value"], + "value": newvalue, } except Exception as e: exception = e @@ -2059,7 +2078,7 @@ def check_cache_contains(self, key, value, append): ## subkey = "hi", value = "test3", overwrite=False ## {"subkey": "hi", "value": ["test2", "test3"]} - def change_cache_subkey(self, key, subkey, value, overwrite): + def change_cache_subkey(self, key, subkey, value, overwrite, category=""): org_id = self.full_execution["workflow"]["execution_org"]["id"] url = "%s/api/v1/orgs/%s/set_cache" % (self.url, org_id) @@ -2081,6 +2100,9 @@ def change_cache_subkey(self, key, subkey, value, overwrite): "value": value, } + if category: + data["category"] = category + response = requests.post(url, json=data, verify=False) try: allvalues = response.json() @@ -2101,10 +2123,10 @@ def change_cache_subkey(self, key, subkey, value, overwrite): self.logger.info("Value couldn't be parsed") return response.text - def delete_cache_value(self, key): - return self.delete_cache(key) + def delete_cache_value(self, key, category=""): + return self.delete_cache(key, category=category) - def get_cache_value(self, key): + def get_cache_value(self, key, category=""): org_id = self.full_execution["workflow"]["execution_org"]["id"] url = "%s/api/v1/orgs/%s/get_cache" % (self.url, org_id) data = { @@ -2115,6 +2137,9 @@ def get_cache_value(self, key): "key": key, } + if category: + data["category"] = category + value = requests.post(url, json=data, verify=False) try: allvalues = value.json() @@ -2138,7 +2163,7 @@ def get_cache_value(self, key): self.logger.info("Value couldn't be parsed, or json dump of value failed") return value.text - def set_cache_value(self, key, value): + def set_cache_value(self, key, value, category=""): org_id = self.full_execution["workflow"]["execution_org"]["id"] url = "%s/api/v1/orgs/%s/set_cache" % (self.url, org_id) @@ -2160,6 +2185,9 @@ def set_cache_value(self, key, value): "value": value, } + if category: + data["category"] = category + response = requests.post(url, json=data, verify=False) try: allvalues = response.json() @@ -2175,6 +2203,9 @@ def set_cache_value(self, key, value): else: allvalues["value"] = str(value) + if category: + allvalues["category"] = category + return json.dumps(allvalues) except: self.logger.info("Value couldn't be parsed") From 6f2af9cf0ef860ca6ca20294233aa9589e58815b Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 28 Feb 2025 00:57:52 +0100 Subject: [PATCH 194/259] Fixed a comma error for recipients in shuffle emails --- email/1.3.0/src/app.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/email/1.3.0/src/app.py b/email/1.3.0/src/app.py index a233ce09..6c9f4f48 100644 --- a/email/1.3.0/src/app.py +++ b/email/1.3.0/src/app.py @@ -67,7 +67,21 @@ def send_email_shuffle(self, apikey, recipients, subject, body): elif "," in recipients: targets = recipients.split(",") - data = {"targets": targets, "body": body, "subject": subject, "type": "alert", "email_app": True} + newtargets = [] + for target in targets: + newtarget = target.strip() + if newtarget: + newtargets.append(newtarget) + + targets = newtargets + + data = { + "targets": targets, + "body": body, + "subject": subject, + "type": "alert", + "email_app": True, + } url = "https://shuffler.io/functions/sendmail" headers = {"Authorization": "Bearer %s" % apikey} From b9896edcf4848b9e3fbb32a61401540877193146 Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 28 Feb 2025 01:00:13 +0100 Subject: [PATCH 195/259] Fixed a remapping problem of CIDR addresses in parse_ioc --- shuffle-tools/1.2.0/src/app.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 40892f0d..f7fae0b2 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -2730,7 +2730,9 @@ def parse_ioc(self, input_string, input_type="all"): # Reformatting IP for item in newarray: - if "ip" in item["data_type"]: + if "cidr" in item["data_type"]: + pass + elif "ip" in item["data_type"]: item["data_type"] = "ip" try: item["is_private_ip"] = ipaddress.ip_address(item["data"]).is_private From 4b045290074ab2e1e1e78bfad667fe6f8f3929ed Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 28 Feb 2025 01:00:30 +0100 Subject: [PATCH 196/259] Removed a print --- shuffle-tools/1.2.0/src/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index f7fae0b2..4707538c 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -2697,7 +2697,7 @@ def parse_ioc(self, input_string, input_type="all"): #iocs = find_iocs(str(input_string), included_ioc_types=ioc_types) iocs = find_iocs(str(input_string)) - self.logger.info("[DEBUG] Found %d ioc types" % len(iocs)) + #self.logger.info("[DEBUG] Found %d ioc types" % len(iocs)) newarray = [] for key, value in iocs.items(): From 8f60962aeb59adffd18850c935c420e4a3674eec Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 13 Mar 2025 14:06:30 +0100 Subject: [PATCH 197/259] Minor print fixes for excel --- microsoft-excel/1.0.0/src/app.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/microsoft-excel/1.0.0/src/app.py b/microsoft-excel/1.0.0/src/app.py index 8baa18f0..433e5627 100644 --- a/microsoft-excel/1.0.0/src/app.py +++ b/microsoft-excel/1.0.0/src/app.py @@ -138,8 +138,8 @@ def convert_to_csv(self, tenant_id, client_id, client_secret, file_id, sheet="Sh # grab the active worksheet ws = wb.active - for item in ws.iter_rows(): - print(item) + #for item in ws.iter_rows(): + # print(item) csvdata = "" for row in ws.values: @@ -156,7 +156,7 @@ def convert_to_csv(self, tenant_id, client_id, client_secret, file_id, sheet="Sh csvdata = csvdata[:-1] print() - print("Data:\n%s\n" % csvdata) + print("Data length:\n(%s)\n" % len(csvdata)) return csvdata From d619c7b81267a5c009b1d1fb2158615d8dcef7b5 Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 13 Mar 2025 14:13:17 +0100 Subject: [PATCH 198/259] Another excel fix --- microsoft-excel/1.0.0/requirements.txt | 3 ++- microsoft-excel/1.0.0/src/app.py | 10 ++-------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/microsoft-excel/1.0.0/requirements.txt b/microsoft-excel/1.0.0/requirements.txt index 451582f8..1cdfbefa 100644 --- a/microsoft-excel/1.0.0/requirements.txt +++ b/microsoft-excel/1.0.0/requirements.txt @@ -1,2 +1,3 @@ -requests==2.25.1 openpyxl==3.0.9 +requests +shuffle-sdk diff --git a/microsoft-excel/1.0.0/src/app.py b/microsoft-excel/1.0.0/src/app.py index 433e5627..8bfae491 100644 --- a/microsoft-excel/1.0.0/src/app.py +++ b/microsoft-excel/1.0.0/src/app.py @@ -1,14 +1,8 @@ -import socket -import asyncio -import time -import random import json -import uuid -import time import requests -from walkoff_app_sdk import csv_parse -from walkoff_app_sdk.app_base import AppBase +from shuffle_sdk import AppBase +from shuffle_sdk import csv_parse from openpyxl import Workbook, load_workbook From f52a587c0eeab36de57ffbb595a9c99a9d99a55c Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 14 Mar 2025 10:21:17 +0100 Subject: [PATCH 199/259] Minor email send changes --- email/1.3.0/src/app.py | 6 +++ microsoft-excel/1.0.0/api.yaml | 23 +++++++++- microsoft-excel/1.0.0/src/app.py | 76 ++++++++++++++++++++++++-------- 3 files changed, 85 insertions(+), 20 deletions(-) diff --git a/email/1.3.0/src/app.py b/email/1.3.0/src/app.py index 6c9f4f48..8ad12abf 100644 --- a/email/1.3.0/src/app.py +++ b/email/1.3.0/src/app.py @@ -95,6 +95,12 @@ def send_email_smtp(self, smtp_host, recipient, subject, body, smtp_port, attach except ValueError: return "SMTP port needs to be a number (Current: %s)" % smtp_port + if "office365.com" in smtp_host: + return { + "success": False, + "reason": "Office 365 does not easily support SMTP anymore, and recommends the Graph API. Please use the Outlook Office365 app in Shuffle with the 'send email' action." + } + self.logger.info("Pre SMTP setup") try: s = smtplib.SMTP(host=smtp_host, port=smtp_port) diff --git a/microsoft-excel/1.0.0/api.yaml b/microsoft-excel/1.0.0/api.yaml index 1ffbb35a..0c27ae28 100644 --- a/microsoft-excel/1.0.0/api.yaml +++ b/microsoft-excel/1.0.0/api.yaml @@ -31,7 +31,7 @@ authentication: type: string actions: - name: get_excel_file_data - description: Gets data from all cells in an excel file as a list. If CSV, returns it as a CSV list + description: Gets data from all cells in an excel file as a list. If CSV, returns it as a CSV list. Max 25.000 lines in total due to timeouts. auth_not_required: true parameters: - name: file_id @@ -40,6 +40,27 @@ actions: required: true schema: type: string + - name: to_list + description: Whether the output should be a list or not + multiline: false + required: false + options: + - true + - false + schema: + type: string + - name: sheets + description: The sheets to use. Comma separated. + multiline: false + required: false + schema: + type: string + - name: max_rows + description: The maximum number of rows to return + multiline: false + required: false + schema: + type: string returns: schema: type: string diff --git a/microsoft-excel/1.0.0/src/app.py b/microsoft-excel/1.0.0/src/app.py index 8bfae491..7031d916 100644 --- a/microsoft-excel/1.0.0/src/app.py +++ b/microsoft-excel/1.0.0/src/app.py @@ -127,9 +127,8 @@ def convert_to_csv(self, tenant_id, client_id, client_secret, file_id, sheet="Sh sheet = "Sheet1" #wb = Workbook(basename) - wb = load_workbook(basename) - print("Sheets: %s" % wb.sheetnames) - + wb = load_workbook(basename, read_only=True) + # grab the active worksheet ws = wb.active #for item in ws.iter_rows(): @@ -149,19 +148,25 @@ def convert_to_csv(self, tenant_id, client_id, client_secret, file_id, sheet="Sh csvdata = csvdata[:-1]+"\n" csvdata = csvdata[:-1] - print() - print("Data length:\n(%s)\n" % len(csvdata)) + print("Data length: (%s)" % len(csvdata)) return csvdata - def get_excel_file_data(self, file_id): + def get_excel_file_data(self, file_id, to_list=True, sheets="", max_rows=100000, skip_rows=0): filedata = self.get_file(file_id) if filedata["success"] != True: - print(f"Bad info from file: {filedata}") + print(f"[ERROR] Bad info from file: {filedata}") return filedata + if not sheets: + sheets = "" + + sheets = sheets.lower() + max_rows = int(max_rows) + skip_rows = int(skip_rows) + try: - print("Filename: %s" % filedata["filename"]) + #print("Filename: %s" % filedata["filename"]) if "csv" in filedata["filename"]: try: filedata["data"] = filedata["data"].decode("utf-8") @@ -183,23 +188,41 @@ def get_excel_file_data(self, file_id): #wb = Workbook(basename) try: - wb = load_workbook(basename) + wb = load_workbook(basename, read_only=True) except Exception as e: return { "success": False, "reason": "The file is invalid. Are you sure it's a valid excel file? CSV files are not supported.", "exception": "Error: %s" % e, } - - print("Sheets: %s" % wb.sheetnames) + # Default + #max_count = 25000 + #if os.getenv("SHUFFLE_APP_SDK_TIMEOUT") > 240: + # Limits are ~no longer relevant if to_list=True + + cnt = 0 + skipped_cnt = 0 output_data = [] for ws in wb.worksheets: - print(f"Title: {ws.title}") + if ws.title.lower() not in sheets and sheets != "": + continue # grab the active worksheet csvdata = "" + if cnt-skipped_cnt > skip_rows: + break + + list_data = [] for row in ws.values: + cnt += 1 + if cnt < skip_rows: + skipped_cnt += 1 + continue + + if cnt-skipped_cnt > max_rows: + break + for value in row: #print(value) if value == None: @@ -209,15 +232,30 @@ def get_excel_file_data(self, file_id): else: csvdata += str(value)+"," - csvdata = csvdata[:-1]+"\n" - csvdata = csvdata[:-1] - - print() - print("Data:\n%s\n" % csvdata) - output_data.append({ + list_data.append(csvdata) + if to_list == False: + csvdata = csvdata[:-1]+"\n" + else: + csvdata = "" + + #csvdata = csvdata[:-1] + + output = { "sheet": ws.title, "data": csvdata, - }) + } + + if to_list == False: + print("Data len (%s): %d" % (ws.title, len(csvdata))) + output_data.append(output) + else: + print("Data len (%s): %d" % (ws.title, len(list_data))) + output_data.append({ + "sheet": ws.title, + "data": list_data, + }) + + print("Done! Returning data of length: %d" % len(output_data)) return output_data From 2ebdf2cbb1853eed04ee38207624ba5dab5c7c9c Mon Sep 17 00:00:00 2001 From: Frikky Date: Sat, 15 Mar 2025 14:51:07 +0100 Subject: [PATCH 200/259] Fixed some run issues with http 1.3.0 --- http/1.3.0/requirements.txt | 2 +- http/1.3.0/src/app.py | 5 +---- http/1.4.0/requirements.txt | 2 +- http/1.4.0/src/app.py | 7 ++----- 4 files changed, 5 insertions(+), 11 deletions(-) diff --git a/http/1.3.0/requirements.txt b/http/1.3.0/requirements.txt index ae3e5391..d91f4447 100644 --- a/http/1.3.0/requirements.txt +++ b/http/1.3.0/requirements.txt @@ -1,2 +1,2 @@ uncurl==0.0.10 -requests==2.25.1 \ No newline at end of file +shuffle_sdk diff --git a/http/1.3.0/src/app.py b/http/1.3.0/src/app.py index 12742c99..0dfb56ca 100755 --- a/http/1.3.0/src/app.py +++ b/http/1.3.0/src/app.py @@ -8,7 +8,7 @@ import requests import subprocess -from walkoff_app_sdk.app_base import AppBase +from shuffle_sdk import AppBase class HTTP(AppBase): __version__ = "1.3.0" @@ -442,10 +442,7 @@ def OPTIONS(self, url, headers="", body="", username="", password="", verify=Tru # Run the actual thing after we've checked params def run(request): - print("Starting cloud!") action = request.get_json() - print(action) - print(type(action)) authorization_key = action.get("authorization") current_execution_id = action.get("execution_id") diff --git a/http/1.4.0/requirements.txt b/http/1.4.0/requirements.txt index ae3e5391..d91f4447 100644 --- a/http/1.4.0/requirements.txt +++ b/http/1.4.0/requirements.txt @@ -1,2 +1,2 @@ uncurl==0.0.10 -requests==2.25.1 \ No newline at end of file +shuffle_sdk diff --git a/http/1.4.0/src/app.py b/http/1.4.0/src/app.py index 981c3cc8..b376c41c 100755 --- a/http/1.4.0/src/app.py +++ b/http/1.4.0/src/app.py @@ -8,10 +8,10 @@ import requests import subprocess -from walkoff_app_sdk.app_base import AppBase +from shuffle_sdk import AppBase class HTTP(AppBase): - __version__ = "1.3.0" + __version__ = "1.4.0" app_name = "http" def __init__(self, redis, logger, console_logger=None): @@ -439,10 +439,7 @@ def OPTIONS(self, url, headers="", body="", username="", password="", verify=Tru # Run the actual thing after we've checked params def run(request): - print("Starting cloud!") action = request.get_json() - print(action) - print(type(action)) authorization_key = action.get("authorization") current_execution_id = action.get("execution_id") From f0ff580e3ad1bd0fcd84f952d082207591b4b4c9 Mon Sep 17 00:00:00 2001 From: Frikky Date: Sat, 15 Mar 2025 15:16:42 +0100 Subject: [PATCH 201/259] Specific http packages --- http/1.3.0/requirements.txt | 2 +- http/1.4.0/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/http/1.3.0/requirements.txt b/http/1.3.0/requirements.txt index d91f4447..a9a6b1c1 100644 --- a/http/1.3.0/requirements.txt +++ b/http/1.3.0/requirements.txt @@ -1,2 +1,2 @@ uncurl==0.0.10 -shuffle_sdk +shuffle_sdk==0.0.18 diff --git a/http/1.4.0/requirements.txt b/http/1.4.0/requirements.txt index d91f4447..a9a6b1c1 100644 --- a/http/1.4.0/requirements.txt +++ b/http/1.4.0/requirements.txt @@ -1,2 +1,2 @@ uncurl==0.0.10 -shuffle_sdk +shuffle_sdk==0.0.18 From c63047a8fde86710aac34d2a8b3b91944f1a7c33 Mon Sep 17 00:00:00 2001 From: Frikky Date: Tue, 18 Mar 2025 19:51:20 +0100 Subject: [PATCH 202/259] Create dependabot.yml --- .github/dependabot.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..339315ad --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" + From d1cbc05633491dd622e8d78fb92aa6a480f02521 Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 2 Apr 2025 11:43:04 +0200 Subject: [PATCH 203/259] Minor app updates --- http/1.3.0/requirements.txt | 2 +- http/1.4.0/requirements.txt | 2 +- shuffle-tools/1.2.0/Dockerfile | 3 +-- shuffle-tools/1.2.0/docker-compose.yml | 15 --------------- shuffle-tools/1.2.0/requirements.txt | 2 +- 5 files changed, 4 insertions(+), 20 deletions(-) delete mode 100644 shuffle-tools/1.2.0/docker-compose.yml diff --git a/http/1.3.0/requirements.txt b/http/1.3.0/requirements.txt index a9a6b1c1..d91f4447 100644 --- a/http/1.3.0/requirements.txt +++ b/http/1.3.0/requirements.txt @@ -1,2 +1,2 @@ uncurl==0.0.10 -shuffle_sdk==0.0.18 +shuffle_sdk diff --git a/http/1.4.0/requirements.txt b/http/1.4.0/requirements.txt index a9a6b1c1..d91f4447 100644 --- a/http/1.4.0/requirements.txt +++ b/http/1.4.0/requirements.txt @@ -1,2 +1,2 @@ uncurl==0.0.10 -shuffle_sdk==0.0.18 +shuffle_sdk diff --git a/shuffle-tools/1.2.0/Dockerfile b/shuffle-tools/1.2.0/Dockerfile index 29ddda0c..40675132 100644 --- a/shuffle-tools/1.2.0/Dockerfile +++ b/shuffle-tools/1.2.0/Dockerfile @@ -11,8 +11,7 @@ RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-de RUN mkdir /install WORKDIR /install COPY requirements.txt /requirements.txt -#RUN pip install --no-cache-dir --prefix="/install" -r /requirements.txt -RUN pip install --prefix="/install" -r /requirements.txt +RUN python3 -m pip install --no-cache-dir --upgrade --prefix="/install" -r /requirements.txt # Switch back to our base image and copy in all of our built packages and source code FROM base diff --git a/shuffle-tools/1.2.0/docker-compose.yml b/shuffle-tools/1.2.0/docker-compose.yml deleted file mode 100644 index e48c6b2f..00000000 --- a/shuffle-tools/1.2.0/docker-compose.yml +++ /dev/null @@ -1,15 +0,0 @@ -version: '3.4' -services: - shuffle-tools: - build: - context: . - dockerfile: Dockerfile -# image: walkoff_registry:5000/walkoff_app_HelloWorld-v1-0 - deploy: - mode: replicated - replicas: 10 - restart_policy: - condition: none - restart: "no" - secrets: - - secret1 diff --git a/shuffle-tools/1.2.0/requirements.txt b/shuffle-tools/1.2.0/requirements.txt index 07e5bca1..4174193b 100644 --- a/shuffle-tools/1.2.0/requirements.txt +++ b/shuffle-tools/1.2.0/requirements.txt @@ -8,5 +8,5 @@ json2xml==5.0.5 ipaddress==1.0.23 google.auth==2.37.0 paramiko==3.5.0 -shuffle-sdk==0.0.13 +shuffle-sdk From e8df2c717db583c9e744d6af6f928356fa9548ed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Apr 2025 10:00:23 +0000 Subject: [PATCH 204/259] Bump the pip group across 68 directories with 5 updates Bumps the pip group with 1 update in the /active-directory/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /archive-org/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /archive-today/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /aws-cloudwatch/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /aws-dynamodb/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /aws-guardduty/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /aws-iam/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /aws-lambda/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /aws-securityhub/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /aws-ses/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /breachsense/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /checkpoint/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 2 updates in the /databasemanager/1.0.0 directory: [requests](https://github.com/psf/requests) and [mysql-connector-python](https://github.com/mysql/mysql-connector-python). Bumps the pip group with 1 update in the /email/1.1.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /email/1.2.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /gitguardian/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /google-chat/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /gpg-tools/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /gpg-tools/1.1.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /gws/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /harfanglab-edr/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /http/1.1.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /http/1.2.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 2 updates in the /mysql/1.0.0 directory: [requests](https://github.com/psf/requests) and [mysql-connector-python](https://github.com/mysql/mysql-connector-python). Bumps the pip group with 1 update in the /netcraft/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 2 updates in the /outlook-exchange/1.0.0 directory: [requests](https://github.com/psf/requests) and [cryptography](https://github.com/pyca/cryptography). Bumps the pip group with 1 update in the /rss/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /secureworks/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /shuffle-subflow/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /shuffle-subflow/1.1.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /shuffle-tools/1.1.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /siemonster/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /sigma/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /snort3/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /sooty/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /twilio/1.9.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/DuoSecurity/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/ad-ldap/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/ansible/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/attack-predictor/1.0.0 directory: [nltk](https://github.com/nltk/nltk). Bumps the pip group with 3 updates in the /unsupported/cylance/1.0.0 directory: [requests](https://github.com/psf/requests), [cryptography](https://github.com/pyca/cryptography) and [pyjwt](https://github.com/jpadilla/pyjwt). Bumps the pip group with 1 update in the /unsupported/email-analyzer/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/hoxhunt/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/lastline/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/microsoft-compliance/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/microsoft-identity-and-access/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/microsoft-intune/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/microsoft-security-and-compliance/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/microsoft-security-oauth2/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/microsoft-teams-system-access/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/microsoft-teams/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/misp/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/nlp/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/office365mgmt/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/passivetotal/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/recordedfuture/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/servicenow/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/shuffle-subflow/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/splunk/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/testing/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/thehive/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/thehive/1.1.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/thehive/1.1.1 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/thehive/1.1.2 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/thehive/1.1.3 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/twitter/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/vulndb/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /yara/1.0.0 directory: [requests](https://github.com/psf/requests). Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `mysql-connector-python` from 8.0.26 to 9.1.0 - [Changelog](https://github.com/mysql/mysql-connector-python/blob/trunk/CHANGES.txt) - [Commits](https://github.com/mysql/mysql-connector-python/compare/8.0.26...9.1.0) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.28.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `mysql-connector-python` from 8.0.23 to 9.1.0 - [Changelog](https://github.com/mysql/mysql-connector-python/blob/trunk/CHANGES.txt) - [Commits](https://github.com/mysql/mysql-connector-python/compare/8.0.26...9.1.0) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `cryptography` from 3.3.2 to 44.0.1 - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/3.3.2...44.0.1) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `nltk` from 3.4.5 to 3.9.1 - [Changelog](https://github.com/nltk/nltk/blob/develop/ChangeLog) - [Commits](https://github.com/nltk/nltk/compare/3.4.5...3.9.1) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `cryptography` from 3.3.2 to 44.0.1 - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/3.3.2...44.0.1) Updates `pyjwt` from 1.7.1 to 2.4.0 - [Release notes](https://github.com/jpadilla/pyjwt/releases) - [Changelog](https://github.com/jpadilla/pyjwt/blob/master/CHANGELOG.rst) - [Commits](https://github.com/jpadilla/pyjwt/compare/1.7.1...2.4.0) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) --- updated-dependencies: - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: mysql-connector-python dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: mysql-connector-python dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: cryptography dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: nltk dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: cryptography dependency-type: direct:production dependency-group: pip - dependency-name: pyjwt dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip ... Signed-off-by: dependabot[bot] --- active-directory/1.0.0/requirements.txt | 2 +- archive-org/1.0.0/requirements.txt | 2 +- archive-today/1.0.0/requirements.txt | 2 +- aws-cloudwatch/1.0.0/requirements.txt | 2 +- aws-dynamodb/1.0.0/requirements.txt | 2 +- aws-guardduty/1.0.0/requirements.txt | 2 +- aws-iam/1.0.0/requirements.txt | 2 +- aws-lambda/1.0.0/requirements.txt | 2 +- aws-securityhub/1.0.0/requirements.txt | 2 +- aws-ses/1.0.0/requirements.txt | 2 +- breachsense/1.0.0/requirements.txt | 2 +- checkpoint/1.0.0/requirements.txt | 2 +- databasemanager/1.0.0/requirements.txt | 4 ++-- email/1.1.0/requirements.txt | 4 ++-- email/1.2.0/requirements.txt | 2 +- gitguardian/1.0.0/requirements.txt | 2 +- google-chat/1.0.0/requirements.txt | 2 +- gpg-tools/1.0.0/requirements.txt | 2 +- gpg-tools/1.1.0/requirements.txt | 2 +- gws/1.0.0/requirements.txt | 2 +- harfanglab-edr/1.0.0/requirements.txt | 2 +- http/1.1.0/requirements.txt | 2 +- http/1.2.0/requirements.txt | 2 +- mysql/1.0.0/requirements.txt | 4 ++-- netcraft/1.0.0/requirements.txt | 2 +- outlook-exchange/1.0.0/requirements.txt | 4 ++-- rss/1.0.0/requirements.txt | 2 +- secureworks/1.0.0/requirements.txt | 2 +- shuffle-subflow/1.0.0/requirements.txt | 2 +- shuffle-subflow/1.1.0/requirements.txt | 2 +- shuffle-tools/1.1.0/requirements.txt | 2 +- siemonster/1.0.0/requirements.txt | 2 +- sigma/1.0.0/requirements.txt | 2 +- snort3/1.0.0/requirements.txt | 2 +- sooty/1.0.0/requirements.txt | 2 +- twilio/1.9.0/requirements.txt | 2 +- unsupported/DuoSecurity/1.0.0/requirements.txt | 2 +- unsupported/ad-ldap/1.0.0/requirements.txt | 2 +- unsupported/ansible/1.0.0/requirements.txt | 2 +- unsupported/attack-predictor/1.0.0/requirements.txt | 2 +- unsupported/cylance/1.0.0/requirements.txt | 6 +++--- unsupported/email-analyzer/1.0.0/requirements.txt | 2 +- unsupported/hoxhunt/1.0.0/requirements.txt | 2 +- unsupported/lastline/1.0.0/requirements.txt | 2 +- unsupported/microsoft-compliance/1.0.0/requirements.txt | 2 +- .../microsoft-identity-and-access/1.0.0/requirements.txt | 2 +- unsupported/microsoft-intune/1.0.0/requirements.txt | 2 +- .../1.0.0/requirements.txt | 2 +- .../microsoft-security-oauth2/1.0.0/requirements.txt | 2 +- .../microsoft-teams-system-access/1.0.0/requirements.txt | 2 +- unsupported/microsoft-teams/1.0.0/requirements.txt | 2 +- unsupported/misp/1.0.0/requirements.txt | 2 +- unsupported/nlp/1.0.0/requirements.txt | 2 +- unsupported/office365mgmt/1.0.0/requirements.txt | 2 +- unsupported/passivetotal/1.0.0/requirements.txt | 2 +- unsupported/recordedfuture/1.0.0/requirements.txt | 2 +- unsupported/servicenow/1.0.0/requirements.txt | 2 +- unsupported/shuffle-subflow/1.0.0/requirements.txt | 2 +- unsupported/splunk/1.0.0/requirements.txt | 2 +- unsupported/testing/1.0.0/requirements.txt | 2 +- unsupported/thehive/1.0.0/requirements.txt | 2 +- unsupported/thehive/1.1.0/requirements.txt | 2 +- unsupported/thehive/1.1.1/requirements.txt | 2 +- unsupported/thehive/1.1.2/requirements.txt | 2 +- unsupported/thehive/1.1.3/requirements.txt | 2 +- unsupported/twitter/1.0.0/requirements.txt | 2 +- unsupported/vulndb/1.0.0/requirements.txt | 2 +- yara/1.0.0/requirements.txt | 2 +- 68 files changed, 74 insertions(+), 74 deletions(-) diff --git a/active-directory/1.0.0/requirements.txt b/active-directory/1.0.0/requirements.txt index 5238833e..f7adbca6 100644 --- a/active-directory/1.0.0/requirements.txt +++ b/active-directory/1.0.0/requirements.txt @@ -1,2 +1,2 @@ ldap3==2.9.1 -requests==2.25.1 +requests==2.32.2 diff --git a/archive-org/1.0.0/requirements.txt b/archive-org/1.0.0/requirements.txt index 01635895..bfb7d916 100644 --- a/archive-org/1.0.0/requirements.txt +++ b/archive-org/1.0.0/requirements.txt @@ -1,2 +1,2 @@ -requests==2.25.1 +requests==2.32.2 savepagenow==1.1.1 \ No newline at end of file diff --git a/archive-today/1.0.0/requirements.txt b/archive-today/1.0.0/requirements.txt index 150b17f3..96e17b90 100644 --- a/archive-today/1.0.0/requirements.txt +++ b/archive-today/1.0.0/requirements.txt @@ -1,2 +1,2 @@ -requests==2.25.1 +requests==2.32.2 archiveis==0.0.9 \ No newline at end of file diff --git a/aws-cloudwatch/1.0.0/requirements.txt b/aws-cloudwatch/1.0.0/requirements.txt index 9c1b76e6..6801915d 100644 --- a/aws-cloudwatch/1.0.0/requirements.txt +++ b/aws-cloudwatch/1.0.0/requirements.txt @@ -1,2 +1,2 @@ boto3==1.20.20 -requests==2.25.1 +requests==2.32.2 diff --git a/aws-dynamodb/1.0.0/requirements.txt b/aws-dynamodb/1.0.0/requirements.txt index 97f3f4de..94f0fa1e 100644 --- a/aws-dynamodb/1.0.0/requirements.txt +++ b/aws-dynamodb/1.0.0/requirements.txt @@ -1,3 +1,3 @@ boto3==1.16.59 bson==0.5.10 -requests==2.25.1 \ No newline at end of file +requests==2.32.2 \ No newline at end of file diff --git a/aws-guardduty/1.0.0/requirements.txt b/aws-guardduty/1.0.0/requirements.txt index f9c46b04..48573434 100644 --- a/aws-guardduty/1.0.0/requirements.txt +++ b/aws-guardduty/1.0.0/requirements.txt @@ -1,2 +1,2 @@ boto3==1.16.59 -requests==2.25.1 +requests==2.32.2 diff --git a/aws-iam/1.0.0/requirements.txt b/aws-iam/1.0.0/requirements.txt index 06ef1c78..c6e7e365 100644 --- a/aws-iam/1.0.0/requirements.txt +++ b/aws-iam/1.0.0/requirements.txt @@ -1,2 +1,2 @@ boto3==1.16.59 -requests==2.25.1 \ No newline at end of file +requests==2.32.2 \ No newline at end of file diff --git a/aws-lambda/1.0.0/requirements.txt b/aws-lambda/1.0.0/requirements.txt index f9c46b04..48573434 100644 --- a/aws-lambda/1.0.0/requirements.txt +++ b/aws-lambda/1.0.0/requirements.txt @@ -1,2 +1,2 @@ boto3==1.16.59 -requests==2.25.1 +requests==2.32.2 diff --git a/aws-securityhub/1.0.0/requirements.txt b/aws-securityhub/1.0.0/requirements.txt index 06ef1c78..c6e7e365 100644 --- a/aws-securityhub/1.0.0/requirements.txt +++ b/aws-securityhub/1.0.0/requirements.txt @@ -1,2 +1,2 @@ boto3==1.16.59 -requests==2.25.1 \ No newline at end of file +requests==2.32.2 \ No newline at end of file diff --git a/aws-ses/1.0.0/requirements.txt b/aws-ses/1.0.0/requirements.txt index d46a14e7..acb2f534 100644 --- a/aws-ses/1.0.0/requirements.txt +++ b/aws-ses/1.0.0/requirements.txt @@ -1,2 +1,2 @@ -requests==2.25.1 +requests==2.32.2 boto3==1.16.59 \ No newline at end of file diff --git a/breachsense/1.0.0/requirements.txt b/breachsense/1.0.0/requirements.txt index fd7d3e06..6b1425c8 100644 --- a/breachsense/1.0.0/requirements.txt +++ b/breachsense/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.25.1 \ No newline at end of file +requests==2.32.2 \ No newline at end of file diff --git a/checkpoint/1.0.0/requirements.txt b/checkpoint/1.0.0/requirements.txt index fd7d3e06..6b1425c8 100644 --- a/checkpoint/1.0.0/requirements.txt +++ b/checkpoint/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.25.1 \ No newline at end of file +requests==2.32.2 \ No newline at end of file diff --git a/databasemanager/1.0.0/requirements.txt b/databasemanager/1.0.0/requirements.txt index fcb1a934..bc69c45e 100644 --- a/databasemanager/1.0.0/requirements.txt +++ b/databasemanager/1.0.0/requirements.txt @@ -1,3 +1,3 @@ -requests==2.25.1 -mysql-connector-python==8.0.26 +requests==2.32.2 +mysql-connector-python==9.1.0 diff --git a/email/1.1.0/requirements.txt b/email/1.1.0/requirements.txt index b18be475..74f24329 100644 --- a/email/1.1.0/requirements.txt +++ b/email/1.1.0/requirements.txt @@ -1,6 +1,6 @@ -requests==2.25.1 +requests==2.32.2 glom==20.11.0 -requests==2.25.1 +requests==2.32.2 eml-parser==1.17.0 msg-parser==1.2.0 mail-parser==3.15.0 diff --git a/email/1.2.0/requirements.txt b/email/1.2.0/requirements.txt index 7f8dacf7..92f3feaf 100644 --- a/email/1.2.0/requirements.txt +++ b/email/1.2.0/requirements.txt @@ -1,4 +1,4 @@ -requests==2.25.1 +requests==2.32.2 glom==20.11.0 eml-parser==1.17.5 msg-parser==1.2.0 diff --git a/gitguardian/1.0.0/requirements.txt b/gitguardian/1.0.0/requirements.txt index 7a453b65..f84e6f34 100644 --- a/gitguardian/1.0.0/requirements.txt +++ b/gitguardian/1.0.0/requirements.txt @@ -1,2 +1,2 @@ -requests==2.25.1 +requests==2.32.2 pygitguardian==1.1.2 \ No newline at end of file diff --git a/google-chat/1.0.0/requirements.txt b/google-chat/1.0.0/requirements.txt index 9d84d358..6e421681 100644 --- a/google-chat/1.0.0/requirements.txt +++ b/google-chat/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.25.1 +requests==2.32.2 diff --git a/gpg-tools/1.0.0/requirements.txt b/gpg-tools/1.0.0/requirements.txt index e5ff88aa..b0258787 100644 --- a/gpg-tools/1.0.0/requirements.txt +++ b/gpg-tools/1.0.0/requirements.txt @@ -1,2 +1,2 @@ python-gnupg==0.4.6 -requests==2.25.1 \ No newline at end of file +requests==2.32.2 \ No newline at end of file diff --git a/gpg-tools/1.1.0/requirements.txt b/gpg-tools/1.1.0/requirements.txt index e5ff88aa..b0258787 100644 --- a/gpg-tools/1.1.0/requirements.txt +++ b/gpg-tools/1.1.0/requirements.txt @@ -1,2 +1,2 @@ python-gnupg==0.4.6 -requests==2.25.1 \ No newline at end of file +requests==2.32.2 \ No newline at end of file diff --git a/gws/1.0.0/requirements.txt b/gws/1.0.0/requirements.txt index 102c8fc3..3a3758d1 100644 --- a/gws/1.0.0/requirements.txt +++ b/gws/1.0.0/requirements.txt @@ -1,4 +1,4 @@ -requests==2.25.1 +requests==2.32.2 google-auth==1.28.0 google-auth-oauthlib==0.4.3 google-auth-httplib2==0.0.4 diff --git a/harfanglab-edr/1.0.0/requirements.txt b/harfanglab-edr/1.0.0/requirements.txt index 414fe428..0e8b805f 100644 --- a/harfanglab-edr/1.0.0/requirements.txt +++ b/harfanglab-edr/1.0.0/requirements.txt @@ -1,4 +1,4 @@ -requests==2.28.1 +requests==2.32.2 python-dateutil==2.8.2 DateTime==4.7 Markdown==3.4.1 diff --git a/http/1.1.0/requirements.txt b/http/1.1.0/requirements.txt index ae3e5391..44c1e933 100644 --- a/http/1.1.0/requirements.txt +++ b/http/1.1.0/requirements.txt @@ -1,2 +1,2 @@ uncurl==0.0.10 -requests==2.25.1 \ No newline at end of file +requests==2.32.2 \ No newline at end of file diff --git a/http/1.2.0/requirements.txt b/http/1.2.0/requirements.txt index ae3e5391..44c1e933 100644 --- a/http/1.2.0/requirements.txt +++ b/http/1.2.0/requirements.txt @@ -1,2 +1,2 @@ uncurl==0.0.10 -requests==2.25.1 \ No newline at end of file +requests==2.32.2 \ No newline at end of file diff --git a/mysql/1.0.0/requirements.txt b/mysql/1.0.0/requirements.txt index 28cc0ef7..7fed93df 100644 --- a/mysql/1.0.0/requirements.txt +++ b/mysql/1.0.0/requirements.txt @@ -1,2 +1,2 @@ -requests==2.25.1 -mysql-connector-python==8.0.23 +requests==2.32.2 +mysql-connector-python==9.1.0 diff --git a/netcraft/1.0.0/requirements.txt b/netcraft/1.0.0/requirements.txt index fd7d3e06..6b1425c8 100644 --- a/netcraft/1.0.0/requirements.txt +++ b/netcraft/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.25.1 \ No newline at end of file +requests==2.32.2 \ No newline at end of file diff --git a/outlook-exchange/1.0.0/requirements.txt b/outlook-exchange/1.0.0/requirements.txt index 46de1688..d7abbf35 100644 --- a/outlook-exchange/1.0.0/requirements.txt +++ b/outlook-exchange/1.0.0/requirements.txt @@ -1,5 +1,5 @@ -cryptography==3.3.2 +cryptography==44.0.1 exchangelib==3.3.2 eml_parser==1.14.4 glom==20.11.0 -requests==2.25.1 +requests==2.32.2 diff --git a/rss/1.0.0/requirements.txt b/rss/1.0.0/requirements.txt index 2da95e3d..3cd0791a 100644 --- a/rss/1.0.0/requirements.txt +++ b/rss/1.0.0/requirements.txt @@ -1,2 +1,2 @@ feedparser==6.0.8 -requests==2.25.1 +requests==2.32.2 diff --git a/secureworks/1.0.0/requirements.txt b/secureworks/1.0.0/requirements.txt index fd7d3e06..6b1425c8 100644 --- a/secureworks/1.0.0/requirements.txt +++ b/secureworks/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.25.1 \ No newline at end of file +requests==2.32.2 \ No newline at end of file diff --git a/shuffle-subflow/1.0.0/requirements.txt b/shuffle-subflow/1.0.0/requirements.txt index fd7d3e06..6b1425c8 100644 --- a/shuffle-subflow/1.0.0/requirements.txt +++ b/shuffle-subflow/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.25.1 \ No newline at end of file +requests==2.32.2 \ No newline at end of file diff --git a/shuffle-subflow/1.1.0/requirements.txt b/shuffle-subflow/1.1.0/requirements.txt index fd7d3e06..6b1425c8 100644 --- a/shuffle-subflow/1.1.0/requirements.txt +++ b/shuffle-subflow/1.1.0/requirements.txt @@ -1 +1 @@ -requests==2.25.1 \ No newline at end of file +requests==2.32.2 \ No newline at end of file diff --git a/shuffle-tools/1.1.0/requirements.txt b/shuffle-tools/1.1.0/requirements.txt index a81ba787..1a201fb6 100644 --- a/shuffle-tools/1.1.0/requirements.txt +++ b/shuffle-tools/1.1.0/requirements.txt @@ -2,7 +2,7 @@ ioc_finder==6.0.1 py7zr==0.11.3 rarfile==4.0 pyminizip==0.2.4 -requests==2.25.1 +requests==2.32.2 xmltodict==0.11.0 json2xml==5.0.5 ipaddress==1.0.23 diff --git a/siemonster/1.0.0/requirements.txt b/siemonster/1.0.0/requirements.txt index fd7d3e06..6b1425c8 100644 --- a/siemonster/1.0.0/requirements.txt +++ b/siemonster/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.25.1 \ No newline at end of file +requests==2.32.2 \ No newline at end of file diff --git a/sigma/1.0.0/requirements.txt b/sigma/1.0.0/requirements.txt index b49a2451..11fb6d4c 100644 --- a/sigma/1.0.0/requirements.txt +++ b/sigma/1.0.0/requirements.txt @@ -1,2 +1,2 @@ -requests==2.25.1 +requests==2.32.2 sigmatools==0.20 diff --git a/snort3/1.0.0/requirements.txt b/snort3/1.0.0/requirements.txt index 64fe70a3..63e7be78 100644 --- a/snort3/1.0.0/requirements.txt +++ b/snort3/1.0.0/requirements.txt @@ -1,2 +1,2 @@ # No extra requirements needed -requests==2.25.1 \ No newline at end of file +requests==2.32.2 \ No newline at end of file diff --git a/sooty/1.0.0/requirements.txt b/sooty/1.0.0/requirements.txt index 897de537..e806671e 100644 --- a/sooty/1.0.0/requirements.txt +++ b/sooty/1.0.0/requirements.txt @@ -1,2 +1,2 @@ -requests==2.25.1 +requests==2.32.2 ipwhois==1.2.0 \ No newline at end of file diff --git a/twilio/1.9.0/requirements.txt b/twilio/1.9.0/requirements.txt index fd7d3e06..6b1425c8 100644 --- a/twilio/1.9.0/requirements.txt +++ b/twilio/1.9.0/requirements.txt @@ -1 +1 @@ -requests==2.25.1 \ No newline at end of file +requests==2.32.2 \ No newline at end of file diff --git a/unsupported/DuoSecurity/1.0.0/requirements.txt b/unsupported/DuoSecurity/1.0.0/requirements.txt index fd7d3e06..6b1425c8 100644 --- a/unsupported/DuoSecurity/1.0.0/requirements.txt +++ b/unsupported/DuoSecurity/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.25.1 \ No newline at end of file +requests==2.32.2 \ No newline at end of file diff --git a/unsupported/ad-ldap/1.0.0/requirements.txt b/unsupported/ad-ldap/1.0.0/requirements.txt index 5238833e..f7adbca6 100644 --- a/unsupported/ad-ldap/1.0.0/requirements.txt +++ b/unsupported/ad-ldap/1.0.0/requirements.txt @@ -1,2 +1,2 @@ ldap3==2.9.1 -requests==2.25.1 +requests==2.32.2 diff --git a/unsupported/ansible/1.0.0/requirements.txt b/unsupported/ansible/1.0.0/requirements.txt index 7b2bf77e..55a4f848 100644 --- a/unsupported/ansible/1.0.0/requirements.txt +++ b/unsupported/ansible/1.0.0/requirements.txt @@ -1,2 +1,2 @@ -requests==2.25.1 +requests==2.32.2 ansible==4.8.0 diff --git a/unsupported/attack-predictor/1.0.0/requirements.txt b/unsupported/attack-predictor/1.0.0/requirements.txt index 2a46021e..aa392935 100644 --- a/unsupported/attack-predictor/1.0.0/requirements.txt +++ b/unsupported/attack-predictor/1.0.0/requirements.txt @@ -1,6 +1,6 @@ colorama==0.4.4 joblib==0.14.1 -nltk==3.4.5 +nltk==3.9.1 numpy==1.17.4 pandas==0.25.3 #scikit-learn==0.22.2.post1 diff --git a/unsupported/cylance/1.0.0/requirements.txt b/unsupported/cylance/1.0.0/requirements.txt index d7aa9605..32286ae4 100644 --- a/unsupported/cylance/1.0.0/requirements.txt +++ b/unsupported/cylance/1.0.0/requirements.txt @@ -1,3 +1,3 @@ -cryptography==3.3.2 -requests==2.25.1 -PyJWT==1.7.1 +cryptography==44.0.1 +requests==2.32.2 +PyJWT==2.4.0 diff --git a/unsupported/email-analyzer/1.0.0/requirements.txt b/unsupported/email-analyzer/1.0.0/requirements.txt index 4965a825..9b077d42 100644 --- a/unsupported/email-analyzer/1.0.0/requirements.txt +++ b/unsupported/email-analyzer/1.0.0/requirements.txt @@ -1,4 +1,4 @@ -requests==2.25.1 +requests==2.32.2 eml-parser==1.14.7 msg-parser==1.2.0 mail-parser==3.15.0 diff --git a/unsupported/hoxhunt/1.0.0/requirements.txt b/unsupported/hoxhunt/1.0.0/requirements.txt index fd7d3e06..6b1425c8 100644 --- a/unsupported/hoxhunt/1.0.0/requirements.txt +++ b/unsupported/hoxhunt/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.25.1 \ No newline at end of file +requests==2.32.2 \ No newline at end of file diff --git a/unsupported/lastline/1.0.0/requirements.txt b/unsupported/lastline/1.0.0/requirements.txt index fd7d3e06..6b1425c8 100644 --- a/unsupported/lastline/1.0.0/requirements.txt +++ b/unsupported/lastline/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.25.1 \ No newline at end of file +requests==2.32.2 \ No newline at end of file diff --git a/unsupported/microsoft-compliance/1.0.0/requirements.txt b/unsupported/microsoft-compliance/1.0.0/requirements.txt index 9d84d358..6e421681 100644 --- a/unsupported/microsoft-compliance/1.0.0/requirements.txt +++ b/unsupported/microsoft-compliance/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.25.1 +requests==2.32.2 diff --git a/unsupported/microsoft-identity-and-access/1.0.0/requirements.txt b/unsupported/microsoft-identity-and-access/1.0.0/requirements.txt index fd7d3e06..6b1425c8 100644 --- a/unsupported/microsoft-identity-and-access/1.0.0/requirements.txt +++ b/unsupported/microsoft-identity-and-access/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.25.1 \ No newline at end of file +requests==2.32.2 \ No newline at end of file diff --git a/unsupported/microsoft-intune/1.0.0/requirements.txt b/unsupported/microsoft-intune/1.0.0/requirements.txt index fd7d3e06..6b1425c8 100644 --- a/unsupported/microsoft-intune/1.0.0/requirements.txt +++ b/unsupported/microsoft-intune/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.25.1 \ No newline at end of file +requests==2.32.2 \ No newline at end of file diff --git a/unsupported/microsoft-security-and-compliance/1.0.0/requirements.txt b/unsupported/microsoft-security-and-compliance/1.0.0/requirements.txt index fd7d3e06..6b1425c8 100644 --- a/unsupported/microsoft-security-and-compliance/1.0.0/requirements.txt +++ b/unsupported/microsoft-security-and-compliance/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.25.1 \ No newline at end of file +requests==2.32.2 \ No newline at end of file diff --git a/unsupported/microsoft-security-oauth2/1.0.0/requirements.txt b/unsupported/microsoft-security-oauth2/1.0.0/requirements.txt index 9d84d358..6e421681 100644 --- a/unsupported/microsoft-security-oauth2/1.0.0/requirements.txt +++ b/unsupported/microsoft-security-oauth2/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.25.1 +requests==2.32.2 diff --git a/unsupported/microsoft-teams-system-access/1.0.0/requirements.txt b/unsupported/microsoft-teams-system-access/1.0.0/requirements.txt index fd7d3e06..6b1425c8 100644 --- a/unsupported/microsoft-teams-system-access/1.0.0/requirements.txt +++ b/unsupported/microsoft-teams-system-access/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.25.1 \ No newline at end of file +requests==2.32.2 \ No newline at end of file diff --git a/unsupported/microsoft-teams/1.0.0/requirements.txt b/unsupported/microsoft-teams/1.0.0/requirements.txt index fd7d3e06..6b1425c8 100644 --- a/unsupported/microsoft-teams/1.0.0/requirements.txt +++ b/unsupported/microsoft-teams/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.25.1 \ No newline at end of file +requests==2.32.2 \ No newline at end of file diff --git a/unsupported/misp/1.0.0/requirements.txt b/unsupported/misp/1.0.0/requirements.txt index fd7d3e06..6b1425c8 100644 --- a/unsupported/misp/1.0.0/requirements.txt +++ b/unsupported/misp/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.25.1 \ No newline at end of file +requests==2.32.2 \ No newline at end of file diff --git a/unsupported/nlp/1.0.0/requirements.txt b/unsupported/nlp/1.0.0/requirements.txt index ade97173..64c303c4 100644 --- a/unsupported/nlp/1.0.0/requirements.txt +++ b/unsupported/nlp/1.0.0/requirements.txt @@ -1,4 +1,4 @@ cyberspacy==1.1.1 tika==1.24 -requests==2.25.1 +requests==2.32.2 spacy==2.3.5 diff --git a/unsupported/office365mgmt/1.0.0/requirements.txt b/unsupported/office365mgmt/1.0.0/requirements.txt index bb17f80b..b086d104 100644 --- a/unsupported/office365mgmt/1.0.0/requirements.txt +++ b/unsupported/office365mgmt/1.0.0/requirements.txt @@ -1,2 +1,2 @@ # No extra requirements needed -requests==2.25.1 +requests==2.32.2 diff --git a/unsupported/passivetotal/1.0.0/requirements.txt b/unsupported/passivetotal/1.0.0/requirements.txt index fd7d3e06..6b1425c8 100644 --- a/unsupported/passivetotal/1.0.0/requirements.txt +++ b/unsupported/passivetotal/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.25.1 \ No newline at end of file +requests==2.32.2 \ No newline at end of file diff --git a/unsupported/recordedfuture/1.0.0/requirements.txt b/unsupported/recordedfuture/1.0.0/requirements.txt index fd7d3e06..6b1425c8 100644 --- a/unsupported/recordedfuture/1.0.0/requirements.txt +++ b/unsupported/recordedfuture/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.25.1 \ No newline at end of file +requests==2.32.2 \ No newline at end of file diff --git a/unsupported/servicenow/1.0.0/requirements.txt b/unsupported/servicenow/1.0.0/requirements.txt index fd7d3e06..6b1425c8 100644 --- a/unsupported/servicenow/1.0.0/requirements.txt +++ b/unsupported/servicenow/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.25.1 \ No newline at end of file +requests==2.32.2 \ No newline at end of file diff --git a/unsupported/shuffle-subflow/1.0.0/requirements.txt b/unsupported/shuffle-subflow/1.0.0/requirements.txt index fd7d3e06..6b1425c8 100644 --- a/unsupported/shuffle-subflow/1.0.0/requirements.txt +++ b/unsupported/shuffle-subflow/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.25.1 \ No newline at end of file +requests==2.32.2 \ No newline at end of file diff --git a/unsupported/splunk/1.0.0/requirements.txt b/unsupported/splunk/1.0.0/requirements.txt index c5a5f6ea..46ff3c1b 100644 --- a/unsupported/splunk/1.0.0/requirements.txt +++ b/unsupported/splunk/1.0.0/requirements.txt @@ -1,2 +1,2 @@ python-magic==0.4.18 -requests==2.25.1 \ No newline at end of file +requests==2.32.2 \ No newline at end of file diff --git a/unsupported/testing/1.0.0/requirements.txt b/unsupported/testing/1.0.0/requirements.txt index fd7d3e06..6b1425c8 100644 --- a/unsupported/testing/1.0.0/requirements.txt +++ b/unsupported/testing/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.25.1 \ No newline at end of file +requests==2.32.2 \ No newline at end of file diff --git a/unsupported/thehive/1.0.0/requirements.txt b/unsupported/thehive/1.0.0/requirements.txt index 1d40c46a..916d3b0d 100644 --- a/unsupported/thehive/1.0.0/requirements.txt +++ b/unsupported/thehive/1.0.0/requirements.txt @@ -1,3 +1,3 @@ -requests==2.25.1 +requests==2.32.2 thehive4py==1.8.1 python-magic==0.4.18 diff --git a/unsupported/thehive/1.1.0/requirements.txt b/unsupported/thehive/1.1.0/requirements.txt index 1d40c46a..916d3b0d 100644 --- a/unsupported/thehive/1.1.0/requirements.txt +++ b/unsupported/thehive/1.1.0/requirements.txt @@ -1,3 +1,3 @@ -requests==2.25.1 +requests==2.32.2 thehive4py==1.8.1 python-magic==0.4.18 diff --git a/unsupported/thehive/1.1.1/requirements.txt b/unsupported/thehive/1.1.1/requirements.txt index 1d40c46a..916d3b0d 100644 --- a/unsupported/thehive/1.1.1/requirements.txt +++ b/unsupported/thehive/1.1.1/requirements.txt @@ -1,3 +1,3 @@ -requests==2.25.1 +requests==2.32.2 thehive4py==1.8.1 python-magic==0.4.18 diff --git a/unsupported/thehive/1.1.2/requirements.txt b/unsupported/thehive/1.1.2/requirements.txt index 1d40c46a..916d3b0d 100644 --- a/unsupported/thehive/1.1.2/requirements.txt +++ b/unsupported/thehive/1.1.2/requirements.txt @@ -1,3 +1,3 @@ -requests==2.25.1 +requests==2.32.2 thehive4py==1.8.1 python-magic==0.4.18 diff --git a/unsupported/thehive/1.1.3/requirements.txt b/unsupported/thehive/1.1.3/requirements.txt index 1d40c46a..916d3b0d 100644 --- a/unsupported/thehive/1.1.3/requirements.txt +++ b/unsupported/thehive/1.1.3/requirements.txt @@ -1,3 +1,3 @@ -requests==2.25.1 +requests==2.32.2 thehive4py==1.8.1 python-magic==0.4.18 diff --git a/unsupported/twitter/1.0.0/requirements.txt b/unsupported/twitter/1.0.0/requirements.txt index 2bb51887..f7c96133 100644 --- a/unsupported/twitter/1.0.0/requirements.txt +++ b/unsupported/twitter/1.0.0/requirements.txt @@ -1,2 +1,2 @@ -requests==2.25.1 +requests==2.32.2 twython==3.9.1 diff --git a/unsupported/vulndb/1.0.0/requirements.txt b/unsupported/vulndb/1.0.0/requirements.txt index 9d84d358..6e421681 100644 --- a/unsupported/vulndb/1.0.0/requirements.txt +++ b/unsupported/vulndb/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.25.1 +requests==2.32.2 diff --git a/yara/1.0.0/requirements.txt b/yara/1.0.0/requirements.txt index bb17f80b..b086d104 100644 --- a/yara/1.0.0/requirements.txt +++ b/yara/1.0.0/requirements.txt @@ -1,2 +1,2 @@ # No extra requirements needed -requests==2.25.1 +requests==2.32.2 From f0d70af643dfb5059167ceee69ee050633d816f2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Apr 2025 15:38:50 +0000 Subject: [PATCH 205/259] Bump the pip group across 9 directories with 6 updates Bumps the pip group with 1 update in the /aws-ec2/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /aws-s3/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /aws-waf/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /cortex/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /http/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 3 updates in the /shuffle-tools/1.0.0 directory: [requests](https://github.com/psf/requests), [py7zr](https://github.com/miurahr/py7zr) and [pyminizip](https://github.com/smihica/pyminizip). Bumps the pip group with 3 updates in the /shuffle-tools/1.1.0 directory: [requests](https://github.com/psf/requests), [py7zr](https://github.com/miurahr/py7zr) and [pyminizip](https://github.com/smihica/pyminizip). Bumps the pip group with 2 updates in the /unsupported/ansible/1.0.0 directory: [requests](https://github.com/psf/requests) and [ansible](https://github.com/ansible-community/ansible-build-data). Bumps the pip group with 2 updates in the /unsupported/attack-predictor/1.0.0 directory: [joblib](https://github.com/joblib/joblib) and [numpy](https://github.com/numpy/numpy). Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `requests` from 2.25.1 to 2.32.2 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `py7zr` from 0.11.3 to 0.20.2 - [Release notes](https://github.com/miurahr/py7zr/releases) - [Changelog](https://github.com/miurahr/py7zr/blob/v0.20.2/Changelog.rst) - [Commits](https://github.com/miurahr/py7zr/compare/v0.11.3...v0.20.2) Updates `pyminizip` from 0.2.4 to 0.2.6 - [Commits](https://github.com/smihica/pyminizip/commits) Updates `requests` from 2.32.2 to 2.32.3 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `py7zr` from 0.11.3 to 0.20.2 - [Release notes](https://github.com/miurahr/py7zr/releases) - [Changelog](https://github.com/miurahr/py7zr/blob/v0.20.2/Changelog.rst) - [Commits](https://github.com/miurahr/py7zr/compare/v0.11.3...v0.20.2) Updates `pyminizip` from 0.2.4 to 0.2.6 - [Commits](https://github.com/smihica/pyminizip/commits) Updates `requests` from 2.32.2 to 2.32.3 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.32.2) Updates `ansible` from 4.8.0 to 8.5.0 - [Changelog](https://github.com/ansible-community/ansible-build-data/blob/main/docs/release-process.md) - [Commits](https://github.com/ansible-community/ansible-build-data/compare/4.8.0...8.5.0) Updates `joblib` from 0.14.1 to 1.2.0 - [Release notes](https://github.com/joblib/joblib/releases) - [Changelog](https://github.com/joblib/joblib/blob/main/CHANGES.rst) - [Commits](https://github.com/joblib/joblib/compare/0.14.1...1.2.0) Updates `numpy` from 1.17.4 to 1.22.0 - [Release notes](https://github.com/numpy/numpy/releases) - [Changelog](https://github.com/numpy/numpy/blob/main/doc/RELEASE_WALKTHROUGH.rst) - [Commits](https://github.com/numpy/numpy/compare/v1.17.4...v1.22.0) --- updated-dependencies: - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: py7zr dependency-type: direct:production dependency-group: pip - dependency-name: pyminizip dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: py7zr dependency-type: direct:production dependency-group: pip - dependency-name: pyminizip dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-type: direct:production dependency-group: pip - dependency-name: ansible dependency-type: direct:production dependency-group: pip - dependency-name: joblib dependency-type: direct:production dependency-group: pip - dependency-name: numpy dependency-type: direct:production dependency-group: pip ... Signed-off-by: dependabot[bot] --- aws-ec2/1.0.0/requirements.txt | 2 +- aws-s3/1.0.0/requirements.txt | 2 +- aws-waf/1.0.0/requirements.txt | 2 +- cortex/1.0.0/requirements.txt | 2 +- http/1.0.0/requirements.txt | 2 +- shuffle-tools/1.0.0/requirements.txt | 6 +++--- shuffle-tools/1.1.0/requirements.txt | 6 +++--- unsupported/ansible/1.0.0/requirements.txt | 4 ++-- unsupported/attack-predictor/1.0.0/requirements.txt | 4 ++-- 9 files changed, 15 insertions(+), 15 deletions(-) diff --git a/aws-ec2/1.0.0/requirements.txt b/aws-ec2/1.0.0/requirements.txt index 9c1b76e6..6801915d 100644 --- a/aws-ec2/1.0.0/requirements.txt +++ b/aws-ec2/1.0.0/requirements.txt @@ -1,2 +1,2 @@ boto3==1.20.20 -requests==2.25.1 +requests==2.32.2 diff --git a/aws-s3/1.0.0/requirements.txt b/aws-s3/1.0.0/requirements.txt index 00eb0244..7c03c548 100644 --- a/aws-s3/1.0.0/requirements.txt +++ b/aws-s3/1.0.0/requirements.txt @@ -1,3 +1,3 @@ boto3==1.16.59 bson==0.5.10 -requests==2.25.1 +requests==2.32.2 diff --git a/aws-waf/1.0.0/requirements.txt b/aws-waf/1.0.0/requirements.txt index f9c46b04..48573434 100644 --- a/aws-waf/1.0.0/requirements.txt +++ b/aws-waf/1.0.0/requirements.txt @@ -1,2 +1,2 @@ boto3==1.16.59 -requests==2.25.1 +requests==2.32.2 diff --git a/cortex/1.0.0/requirements.txt b/cortex/1.0.0/requirements.txt index 1f296044..c1b6101c 100644 --- a/cortex/1.0.0/requirements.txt +++ b/cortex/1.0.0/requirements.txt @@ -1,3 +1,3 @@ -requests==2.25.1 +requests==2.32.2 python-magic==0.4.18 cortex4py==2.0.1 diff --git a/http/1.0.0/requirements.txt b/http/1.0.0/requirements.txt index ae3e5391..44c1e933 100644 --- a/http/1.0.0/requirements.txt +++ b/http/1.0.0/requirements.txt @@ -1,2 +1,2 @@ uncurl==0.0.10 -requests==2.25.1 \ No newline at end of file +requests==2.32.2 \ No newline at end of file diff --git a/shuffle-tools/1.0.0/requirements.txt b/shuffle-tools/1.0.0/requirements.txt index a81ba787..5e4e9e11 100644 --- a/shuffle-tools/1.0.0/requirements.txt +++ b/shuffle-tools/1.0.0/requirements.txt @@ -1,8 +1,8 @@ ioc_finder==6.0.1 -py7zr==0.11.3 +py7zr==0.20.2 rarfile==4.0 -pyminizip==0.2.4 -requests==2.25.1 +pyminizip==0.2.6 +requests==2.32.2 xmltodict==0.11.0 json2xml==5.0.5 ipaddress==1.0.23 diff --git a/shuffle-tools/1.1.0/requirements.txt b/shuffle-tools/1.1.0/requirements.txt index 1a201fb6..1008589f 100644 --- a/shuffle-tools/1.1.0/requirements.txt +++ b/shuffle-tools/1.1.0/requirements.txt @@ -1,8 +1,8 @@ ioc_finder==6.0.1 -py7zr==0.11.3 +py7zr==0.20.2 rarfile==4.0 -pyminizip==0.2.4 -requests==2.32.2 +pyminizip==0.2.6 +requests==2.32.3 xmltodict==0.11.0 json2xml==5.0.5 ipaddress==1.0.23 diff --git a/unsupported/ansible/1.0.0/requirements.txt b/unsupported/ansible/1.0.0/requirements.txt index 55a4f848..e1176e47 100644 --- a/unsupported/ansible/1.0.0/requirements.txt +++ b/unsupported/ansible/1.0.0/requirements.txt @@ -1,2 +1,2 @@ -requests==2.32.2 -ansible==4.8.0 +requests==2.32.3 +ansible==8.5.0 diff --git a/unsupported/attack-predictor/1.0.0/requirements.txt b/unsupported/attack-predictor/1.0.0/requirements.txt index aa392935..5687cab1 100644 --- a/unsupported/attack-predictor/1.0.0/requirements.txt +++ b/unsupported/attack-predictor/1.0.0/requirements.txt @@ -1,7 +1,7 @@ colorama==0.4.4 -joblib==0.14.1 +joblib==1.2.0 nltk==3.9.1 -numpy==1.17.4 +numpy==1.22.0 pandas==0.25.3 #scikit-learn==0.22.2.post1 stix2==1.2.1 From e657f00db43496eadaa9f61e4e71d2719b6b8fc3 Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 3 Apr 2025 00:26:58 +0200 Subject: [PATCH 206/259] Rebuild --- .github/workflows/{ci.yaml => dockerbuild.yaml} | 0 email/1.3.0/src/app.py | 1 - 2 files changed, 1 deletion(-) rename .github/workflows/{ci.yaml => dockerbuild.yaml} (100%) diff --git a/.github/workflows/ci.yaml b/.github/workflows/dockerbuild.yaml similarity index 100% rename from .github/workflows/ci.yaml rename to .github/workflows/dockerbuild.yaml diff --git a/email/1.3.0/src/app.py b/email/1.3.0/src/app.py index 8ad12abf..aabb1bbe 100644 --- a/email/1.3.0/src/app.py +++ b/email/1.3.0/src/app.py @@ -19,7 +19,6 @@ from email.mime.text import MIMEText from email.mime.application import MIMEApplication -#from walkoff_app_sdk.app_base import AppBase from shuffle_sdk import AppBase def json_serial(obj): From 4e5b712d51206f1a6c7e730dc8a819e728053c84 Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 3 Apr 2025 00:32:24 +0200 Subject: [PATCH 207/259] Rebuild again --- .github/workflows/dockerbuild.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/dockerbuild.yaml b/.github/workflows/dockerbuild.yaml index aa536619..1cd3cdbf 100644 --- a/.github/workflows/dockerbuild.yaml +++ b/.github/workflows/dockerbuild.yaml @@ -38,7 +38,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up QEMU uses: docker/setup-qemu-action@v1 @@ -47,9 +47,9 @@ jobs: uses: docker/setup-buildx-action@v1 - name: Login to DockerHub - uses: docker/login-action@v1 + uses: docker/login-action@v3 with: - username: ${{ secrets.DOCKERHUB_LOGIN_USER }} + username: frikky password: ${{ secrets.DOCKERHUB_TOKEN }} # Use below configuration for ghcr.io # with: @@ -60,7 +60,7 @@ jobs: name: Build and push Master if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} id: docker_build_master - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v6 with: context: ${{ matrix.app }}/${{ matrix.version }} file: ${{ matrix.app }}/${{ matrix.version }}/Dockerfile @@ -74,7 +74,7 @@ jobs: name: Build and push Feature PR if: ${{ github.event_name == 'pull_request' }} id: docker_build_feature - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v6 with: context: ${{ matrix.app }}/${{ matrix.version }} file: ${{ matrix.app }}/${{ matrix.version }}/Dockerfile From 8e1fbdf12218b9142c0185b5c7e6a6dbd5c3a8c3 Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 3 Apr 2025 00:34:11 +0200 Subject: [PATCH 208/259] And again --- .github/workflows/dockerbuild.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/dockerbuild.yaml b/.github/workflows/dockerbuild.yaml index 1cd3cdbf..5c5d1607 100644 --- a/.github/workflows/dockerbuild.yaml +++ b/.github/workflows/dockerbuild.yaml @@ -1,4 +1,4 @@ -name: ci +name: dockerbuild on: push: @@ -49,7 +49,7 @@ jobs: name: Login to DockerHub uses: docker/login-action@v3 with: - username: frikky + username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} # Use below configuration for ghcr.io # with: @@ -67,9 +67,9 @@ jobs: platforms: linux/amd64,linux/arm64 push: true tags: | - ${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.app }}:${{ matrix.version }} - ${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.app }}:latest - ${{ secrets.DOCKERHUB_USERNAME }}/shuffle:${{ matrix.app }}_${{ matrix.version }} + frikky/${{ matrix.app }}:${{ matrix.version }} + frikky/${{ matrix.app }}:latest + frikky/shuffle:${{ matrix.app }}_${{ matrix.version }} - name: Build and push Feature PR if: ${{ github.event_name == 'pull_request' }} @@ -81,7 +81,7 @@ jobs: platforms: linux/amd64,linux/arm64,linux/386 push: true tags: | - ${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.app }}:${{ github.head_ref }} + frikky/${{ matrix.app }}:${{ github.head_ref }} - name: Image digest run: | From 1122251237aae49054ec402ecb5c4335157f7f4c Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 3 Apr 2025 00:36:50 +0200 Subject: [PATCH 209/259] Another simplified rebuild --- .github/workflows/dockerbuild.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/dockerbuild.yaml b/.github/workflows/dockerbuild.yaml index 5c5d1607..a007b518 100644 --- a/.github/workflows/dockerbuild.yaml +++ b/.github/workflows/dockerbuild.yaml @@ -23,6 +23,13 @@ jobs: ret = [] for i in flist: a,v = i.split('/') + # Look for folders only + if 'unsupported' in v: + continue + + if '.md' in v: + continue + ret.append({'app':a, 'version':v }) print(json.dumps({'include': ret})) EOF From 66ec45ae714e415630ad828682a4ae87def8ee0a Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 3 Apr 2025 00:39:25 +0200 Subject: [PATCH 210/259] Another simplification --- .github/workflows/dockerbuild.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dockerbuild.yaml b/.github/workflows/dockerbuild.yaml index a007b518..eaf2d1b0 100644 --- a/.github/workflows/dockerbuild.yaml +++ b/.github/workflows/dockerbuild.yaml @@ -24,7 +24,7 @@ jobs: for i in flist: a,v = i.split('/') # Look for folders only - if 'unsupported' in v: + if 'unsupported' in v or 'unsupported' in a: continue if '.md' in v: From c58797103bd786a0e977744f8cd323b8a058705d Mon Sep 17 00:00:00 2001 From: yashsinghcodes Date: Tue, 22 Apr 2025 23:03:18 +0530 Subject: [PATCH 211/259] output stderr when running ssh commands --- shuffle-tools/1.2.0/src/app.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 4707538c..6ae688cd 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -2632,6 +2632,11 @@ def run_ssh_command(self, host, port, user_name, private_key_file_id, password, try: stdin, stdout, stderr = ssh_client.exec_command(str(command)) + + stderr_ouput = stderr.read().decode(errors='ignore') + + if stderr_ouput: + return {"success": "true", "message": stderr_ouput} except Exception as e: return {"success":"false","message":str(e)} From 3f2bd418265e2096fb3aa941ce1934e0a09f7934 Mon Sep 17 00:00:00 2001 From: yashsinghcodes Date: Mon, 5 May 2025 19:09:34 +0530 Subject: [PATCH 212/259] provide stderr for ssh commands --- shuffle-tools/1.2.0/src/app.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 6ae688cd..233e1aab 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -2633,14 +2633,10 @@ def run_ssh_command(self, host, port, user_name, private_key_file_id, password, try: stdin, stdout, stderr = ssh_client.exec_command(str(command)) - stderr_ouput = stderr.read().decode(errors='ignore') - - if stderr_ouput: - return {"success": "true", "message": stderr_ouput} except Exception as e: return {"success":"false","message":str(e)} - return {"success":"true","output": stdout.read().decode(errors='ignore')} + return {"success":"true","output": stdout.read().decode(errors='ignore'), "error_log": stderr.read().decode(errors='ignore')} def cleanup_ioc_data(self, input_data): # Remove unecessary parts like { and }, quotes etc From de2914ee5179b8734e61287a1957af2d7bc770fd Mon Sep 17 00:00:00 2001 From: yashsinghcodes Date: Mon, 5 May 2025 19:10:38 +0530 Subject: [PATCH 213/259] fixed grammar --- shuffle-tools/1.2.0/src/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 233e1aab..ecdf1630 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -2636,7 +2636,7 @@ def run_ssh_command(self, host, port, user_name, private_key_file_id, password, except Exception as e: return {"success":"false","message":str(e)} - return {"success":"true","output": stdout.read().decode(errors='ignore'), "error_log": stderr.read().decode(errors='ignore')} + return {"success":"true","output": stdout.read().decode(errors='ignore'), "error_logs": stderr.read().decode(errors='ignore')} def cleanup_ioc_data(self, input_data): # Remove unecessary parts like { and }, quotes etc From 63990e284848052e59d9e617967fca90ef5c65b0 Mon Sep 17 00:00:00 2001 From: Frikky Date: Tue, 20 May 2025 12:48:22 +0200 Subject: [PATCH 214/259] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 04ed8a33..a8f01df6 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # Shuffle Apps -All public apps are available in the search, engine either in your local instance or on [https://shuffler.io/search?tab=apps](https://shuffler.io/search?tab=apps). This is a repository for apps to be used in [Shuffle](https://github.com/shuffle/shuffle) +All public apps are available in the search, engine either in your local instance or on [https://shuffler.io/search?tab=apps](https://shuffler.io/search). This is a repository for apps to be used in [Shuffle](https://github.com/shuffle/shuffle) **PS:** These apps should be valid with WALKOFF (from NSA), but the SDK is different, meaning you have to change the FIRST line in each Dockerfile (FROM shuffle/shuffle:app_sdk) to make it compatible with Shuffle. ## App Creation -App creation can be done with the Shuffle App Creator (exports as OpenAPI) or Python, which makes it possible to connect _literally_ any tool. Always prioritize using the App Creator when applicable. +App creation can be done with the Shuffle App Creator (exports as OpenAPI), with AI Generation, or Python - which makes it possible to connect _literally_ any tool. Always prioritize using the App Creator when applicable, as it makes maintaining an app easier. ![Shuffle-workflow-categories](https://github.com/shuffle/shuffle-workflows/blob/master/images/categories_circle_dark.png) From c3e429e1331ed5351b0787cd140b47a2b39d0d98 Mon Sep 17 00:00:00 2001 From: Lellek <47337504+Lellek@users.noreply.github.com> Date: Wed, 21 May 2025 09:54:00 +0200 Subject: [PATCH 215/259] added app for basic redis integration --- redis/1.0.0/Dockerfile | 27 +++++++++ redis/1.0.0/api.yaml | 110 +++++++++++++++++++++++++++++++++++ redis/1.0.0/app.zip | Bin 0 -> 4586 bytes redis/1.0.0/requirements.txt | 1 + redis/1.0.0/src/app.py | 58 ++++++++++++++++++ 5 files changed, 196 insertions(+) create mode 100644 redis/1.0.0/Dockerfile create mode 100644 redis/1.0.0/api.yaml create mode 100644 redis/1.0.0/app.zip create mode 100644 redis/1.0.0/requirements.txt create mode 100644 redis/1.0.0/src/app.py diff --git a/redis/1.0.0/Dockerfile b/redis/1.0.0/Dockerfile new file mode 100644 index 00000000..9bbc5110 --- /dev/null +++ b/redis/1.0.0/Dockerfile @@ -0,0 +1,27 @@ +# Base our app image off of the WALKOFF App SDK image +FROM frikky/shuffle:app_sdk as base + +# We're going to stage away all of the bloat from the build tools so lets create a builder stage +FROM base as builder + +# Install all alpine build tools needed for our pip installs +RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev + +# Install all of our pip packages in a single directory that we can copy to our base image later +RUN mkdir /install +WORKDIR /install +COPY requirements.txt /requirements.txt +RUN pip install --prefix="/install" -r /requirements.txt + +# Switch back to our base image and copy in all of our built packages and source code +FROM base +COPY --from=builder /install /usr/local +COPY src /app +RUN apk add curl + +# Install any binary dependencies needed in our final image +# RUN apk --no-cache add --update my_binary_dependency + +# Finally, lets run our app! +WORKDIR /app +CMD python app.py --log-level DEBUG diff --git a/redis/1.0.0/api.yaml b/redis/1.0.0/api.yaml new file mode 100644 index 00000000..0ee97122 --- /dev/null +++ b/redis/1.0.0/api.yaml @@ -0,0 +1,110 @@ +app_version: 1.0.0 +name: Redis +description: Redis integration. +tags: + - redis +categories: + - Other +# contact_info: +# name: "@frikkylikeme" +# url: https://shuffler.io +# email: frikky@shuffler.io +authentication: + required: true + parameters: + - name: server + description: Redis server ip + example: "127.0.0.1" + required: true + schema: + type: string + - name: port + description: Redis port + example: "6379" + required: true + schema: + type: string + - name: password + description: redis password + example: "*****" + required: false + schema: + type: string + - name: database + description: redis database + example: "0" + required: false + options: + - 0 + - 1 + - 2 + - 3 + - 4 + - 5 + - 6 + - 7 + - 8 + - 9 + - 10 + - 11 + - 12 + - 13 + - 14 + - 15 + schema: + type: string + +actions: + - name: set_value + description: Set a key value pair + parameters: + - name: key + description: Key name + required: true + multiline: false + example: "my key" + schema: + type: string + - name: value + description: Value + required: true + multiline: true + example: 'my value' + schema: + type: string + - name: nx + description: Set value only if not exists + required: true + options: + - "false" + - "true" + multiline: false + example: "true" + schema: + type: bool + - name: ex + description: Expiration time in seconds + required: false + multiline: false + example: '60' + schema: + type: string + returns: + schema: + type: string + + - name: get_value + description: Get value of a key + parameters: + - name: key + description: Key name + required: true + multiline: false + example: "my key" + schema: + type: string + returns: + schema: + type: string +large_image: data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IS0tIFVwbG9hZGVkIHRvOiBTVkcgUmVwbywgd3d3LnN2Z3JlcG8uY29tLCBHZW5lcmF0b3I6IFNWRyBSZXBvIE1peGVyIFRvb2xzIC0tPgo8c3ZnIHdpZHRoPSI4MDBweCIgaGVpZ2h0PSI4MDBweCIgdmlld0JveD0iMCAtMTggMjU2IDI1NiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pbiBtZWV0Ij48cGF0aCBkPSJNMjQ1Ljk3IDE2OC45NDNjLTEzLjY2MiA3LjEyMS04NC40MzQgMzYuMjItOTkuNTAxIDQ0LjA3NS0xNS4wNjcgNy44NTYtMjMuNDM3IDcuNzgtMzUuMzQgMi4wOS0xMS45MDItNS42OS04Ny4yMTYtMzYuMTEyLTEwMC43ODMtNDIuNTk3QzMuNTY2IDE2OS4yNzEgMCAxNjYuNTM1IDAgMTYzLjk1MXYtMjUuODc2czk4LjA1LTIxLjM0NSAxMTMuODc5LTI3LjAyNGMxNS44MjgtNS42NzkgMjEuMzItNS44ODQgMzQuNzktLjk1IDEzLjQ3MiA0LjkzNiA5NC4wMTggMTkuNDY4IDEwNy4zMzEgMjQuMzQ0bC0uMDA2IDI1LjUxYy4wMDIgMi41NTgtMy4wNyA1LjM2NC0xMC4wMjQgOC45ODgiIGZpbGw9IiM5MTI2MjYiLz48cGF0aCBkPSJNMjQ1Ljk2NSAxNDMuMjJjLTEzLjY2MSA3LjExOC04NC40MzEgMzYuMjE4LTk5LjQ5OCA0NC4wNzItMTUuMDY2IDcuODU3LTIzLjQzNiA3Ljc4LTM1LjMzOCAyLjA5LTExLjkwMy01LjY4Ni04Ny4yMTQtMzYuMTEzLTEwMC43OC00Mi41OTQtMTMuNTY2LTYuNDg1LTEzLjg1LTEwLjk0OC0uNTI0LTE2LjE2NiAxMy4zMjYtNS4yMiA4OC4yMjQtMzQuNjA1IDEwNC4wNTUtNDAuMjg0IDE1LjgyOC01LjY3NyAyMS4zMTktNS44ODQgMzQuNzg5LS45NDggMTMuNDcxIDQuOTM0IDgzLjgxOSAzMi45MzUgOTcuMTMgMzcuODEgMTMuMzE2IDQuODgxIDEzLjgyNyA4LjkuMTY2IDE2LjAyIiBmaWxsPSIjQzYzMDJCIi8+PHBhdGggZD0iTTI0NS45NyAxMjcuMDc0Yy0xMy42NjIgNy4xMjItODQuNDM0IDM2LjIyLTk5LjUwMSA0NC4wNzgtMTUuMDY3IDcuODUzLTIzLjQzNyA3Ljc3Ny0zNS4zNCAyLjA4Ny0xMS45MDMtNS42ODctODcuMjE2LTM2LjExMi0xMDAuNzgzLTQyLjU5N0MzLjU2NiAxMjcuNDAyIDAgMTI0LjY3IDAgMTIyLjA4NVY5Ni4yMDZzOTguMDUtMjEuMzQ0IDExMy44NzktMjcuMDIzYzE1LjgyOC01LjY3OSAyMS4zMi01Ljg4NSAzNC43OS0uOTVDMTYyLjE0MiA3My4xNjggMjQyLjY4OCA4Ny42OTcgMjU2IDkyLjU3NGwtLjAwNiAyNS41MTNjLjAwMiAyLjU1Ny0zLjA3IDUuMzYzLTEwLjAyNCA4Ljk4NyIgZmlsbD0iIzkxMjYyNiIvPjxwYXRoIGQ9Ik0yNDUuOTY1IDEwMS4zNTFjLTEzLjY2MSA3LjEyLTg0LjQzMSAzNi4yMTgtOTkuNDk4IDQ0LjA3NS0xNS4wNjYgNy44NTQtMjMuNDM2IDcuNzc3LTM1LjMzOCAyLjA4Ny0xMS45MDMtNS42ODYtODcuMjE0LTM2LjExMi0xMDAuNzgtNDIuNTk0LTEzLjU2Ni02LjQ4My0xMy44NS0xMC45NDctLjUyNC0xNi4xNjdDMjMuMTUxIDgzLjUzNSA5OC4wNSA1NC4xNDggMTEzLjg4IDQ4LjQ3YzE1LjgyOC01LjY3OCAyMS4zMTktNS44ODQgMzQuNzg5LS45NDkgMTMuNDcxIDQuOTM0IDgzLjgxOSAzMi45MzMgOTcuMTMgMzcuODEgMTMuMzE2IDQuODggMTMuODI3IDguOS4xNjYgMTYuMDIiIGZpbGw9IiNDNjMwMkIiLz48cGF0aCBkPSJNMjQ1Ljk3IDgzLjY1M2MtMTMuNjYyIDcuMTItODQuNDM0IDM2LjIyLTk5LjUwMSA0NC4wNzgtMTUuMDY3IDcuODU0LTIzLjQzNyA3Ljc3Ny0zNS4zNCAyLjA4Ny0xMS45MDMtNS42ODctODcuMjE2LTM2LjExMy0xMDAuNzgzLTQyLjU5NUMzLjU2NiA4My45OCAwIDgxLjI0NyAwIDc4LjY2NXYtMjUuODhzOTguMDUtMjEuMzQzIDExMy44NzktMjcuMDIxYzE1LjgyOC01LjY4IDIxLjMyLTUuODg0IDM0Ljc5LS45NUMxNjIuMTQyIDI5Ljc0OSAyNDIuNjg4IDQ0LjI3OCAyNTYgNDkuMTU1bC0uMDA2IDI1LjUxMmMuMDAyIDIuNTU1LTMuMDcgNS4zNjEtMTAuMDI0IDguOTg2IiBmaWxsPSIjOTEyNjI2Ii8+PHBhdGggZD0iTTI0NS45NjUgNTcuOTNjLTEzLjY2MSA3LjEyLTg0LjQzMSAzNi4yMi05OS40OTggNDQuMDc0LTE1LjA2NiA3Ljg1NC0yMy40MzYgNy43NzctMzUuMzM4IDIuMDlDOTkuMjI3IDk4LjQwNCAyMy45MTUgNjcuOTggMTAuMzUgNjEuNDk3LTMuMjE3IDU1LjAxNS0zLjUgNTAuNTUgOS44MjUgNDUuMzMxIDIzLjE1MSA0MC4xMTMgOTguMDUgMTAuNzMgMTEzLjg4IDUuMDVjMTUuODI4LTUuNjc5IDIxLjMxOS01Ljg4MyAzNC43ODktLjk0OCAxMy40NzEgNC45MzUgODMuODE5IDMyLjkzNCA5Ny4xMyAzNy44MTEgMTMuMzE2IDQuODc2IDEzLjgyNyA4Ljg5Ny4xNjYgMTYuMDE3IiBmaWxsPSIjQzYzMDJCIi8+PHBhdGggZD0iTTE1OS4yODMgMzIuNzU3bC0yMi4wMSAyLjI4NS00LjkyNyAxMS44NTYtNy45NTgtMTMuMjMtMjUuNDE1LTIuMjg0IDE4Ljk2NC02LjgzOS01LjY5LTEwLjQ5OCAxNy43NTUgNi45NDQgMTYuNzM4LTUuNDgtNC41MjQgMTAuODU1IDE3LjA2NyA2LjM5MU0xMzEuMDMyIDkwLjI3NUw4OS45NTUgNzMuMjM4bDU4Ljg2LTkuMDM1LTE3Ljc4MyAyNi4wNzJNNzQuMDgyIDM5LjM0N2MxNy4zNzUgMCAzMS40NiA1LjQ2IDMxLjQ2IDEyLjE5NCAwIDYuNzM2LTE0LjA4NSAxMi4xOTUtMzEuNDYgMTIuMTk1cy0zMS40Ni01LjQ2LTMxLjQ2LTEyLjE5NWMwLTYuNzM0IDE0LjA4NS0xMi4xOTQgMzEuNDYtMTIuMTk0IiBmaWxsPSIjRkZGIi8+PHBhdGggZD0iTTE4NS4yOTUgMzUuOTk4bDM0LjgzNiAxMy43NjYtMzQuODA2IDEzLjc1My0uMDMtMjcuNTIiIGZpbGw9IiM2MjFCMUMiLz48cGF0aCBkPSJNMTQ2Ljc1NSA1MS4yNDNsMzguNTQtMTUuMjQ1LjAzIDI3LjUxOS0zLjc3OSAxLjQ3OC0zNC43OTEtMTMuNzUyIiBmaWxsPSIjOUEyOTI4Ii8+PC9zdmc+ +# yamllint disable-line rule:line-length diff --git a/redis/1.0.0/app.zip b/redis/1.0.0/app.zip new file mode 100644 index 0000000000000000000000000000000000000000..670f84561cced73357486591a35ab527b97414a1 GIT binary patch literal 4586 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 literal 0 HcmV?d00001 diff --git a/redis/1.0.0/requirements.txt b/redis/1.0.0/requirements.txt new file mode 100644 index 00000000..c6889d58 --- /dev/null +++ b/redis/1.0.0/requirements.txt @@ -0,0 +1 @@ +redis==5.2.1 \ No newline at end of file diff --git a/redis/1.0.0/src/app.py b/redis/1.0.0/src/app.py new file mode 100644 index 00000000..9a60449a --- /dev/null +++ b/redis/1.0.0/src/app.py @@ -0,0 +1,58 @@ +import json +import ast +import redis + +from walkoff_app_sdk.app_base import AppBase + +class REDIS(AppBase): + __version__ = "1.0.0" + app_name = "Redis" + + def __init__(self, redis, logger, console_logger=None): + print("INIT") + """ + Each app should have this __init__ to set up Redis and logging. + :param redis: + :param logger: + :param console_logger: + """ + super().__init__(redis, logger, console_logger) + + def set_value(self, server, port, key, value, nx, ex = None, password = None, database = 0): + """ + Sets a key-value pair in Redis. + """ + + if password == None: + redis_client = redis.Redis(decode_responses=True, host=server, port=port, db=database) + else: + redis_client = redis.Redis(decode_responses=True, password=password, port=port, host=server, db=database) + + + result = redis_client.set(name=key, value=value, nx=nx, ex=ex) # nx=True ensures "set only if the key does not exist" + print(result) + if result: # If result is True, the key was successfully set + print(f"Success: Key {key} set with value '{value}'") + return {"success": True} + else: + print(f"Failed: Key {key} already exists.") + return {"success": False} + + def get_value(self, server, port, key, password = None, database = 0): + """ + Gets a value for a key in Redis. + """ + if password == None: + redis_client = redis.Redis(decode_responses=True, host=server, port=port, db=database) + else: + redis_client = redis.Redis(decode_responses=True, password=password, port=port, host=server, db=database) + + result = redis_client.get(name=key) + if result: + return {"success": True, "value": result} + else: + return {"success": False, "error": f"Key {key} does not exist", "value": None} + + +if __name__ == "__main__": + REDIS.run() From 1bd10c013cc5001d500404a970d0d569fdaf986f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Jun 2025 08:46:02 +0000 Subject: [PATCH 216/259] Bump the pip group across 68 directories with 1 update Bumps the pip group with 1 update in the /active-directory/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /archive-org/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /archive-today/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /aws-cloudwatch/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /aws-dynamodb/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /aws-guardduty/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /aws-iam/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /aws-lambda/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /aws-securityhub/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /aws-ses/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /breachsense/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /checkpoint/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /databasemanager/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /email/1.1.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /email/1.2.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /gitguardian/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /google-chat/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /gpg-tools/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /gpg-tools/1.1.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /gws/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /harfanglab-edr/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /http/1.1.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /http/1.2.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /mysql/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /netcraft/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /outlook-exchange/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /rss/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /secureworks/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /shuffle-subflow/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /shuffle-subflow/1.1.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /shuffle-tools/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /shuffle-tools/1.1.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /shuffle-tools/1.2.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /siemonster/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /sigma/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /snort3/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /sooty/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /twilio/1.9.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/DuoSecurity/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/ad-ldap/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/cylance/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/email-analyzer/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/hoxhunt/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/lastline/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/microsoft-compliance/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/microsoft-identity-and-access/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/microsoft-intune/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/microsoft-security-and-compliance/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/microsoft-security-oauth2/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/microsoft-teams-system-access/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/microsoft-teams/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/misp/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/nlp/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/office365mgmt/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/passivetotal/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/recordedfuture/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/servicenow/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/shuffle-subflow/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/splunk/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/testing/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/thehive/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/thehive/1.1.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/thehive/1.1.1 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/thehive/1.1.2 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/thehive/1.1.3 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/twitter/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/vulndb/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /yara/1.0.0 directory: [requests](https://github.com/psf/requests). Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.3 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.3 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) --- updated-dependencies: - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip ... Signed-off-by: dependabot[bot] --- active-directory/1.0.0/requirements.txt | 2 +- archive-org/1.0.0/requirements.txt | 2 +- archive-today/1.0.0/requirements.txt | 2 +- aws-cloudwatch/1.0.0/requirements.txt | 2 +- aws-dynamodb/1.0.0/requirements.txt | 2 +- aws-guardduty/1.0.0/requirements.txt | 2 +- aws-iam/1.0.0/requirements.txt | 2 +- aws-lambda/1.0.0/requirements.txt | 2 +- aws-securityhub/1.0.0/requirements.txt | 2 +- aws-ses/1.0.0/requirements.txt | 2 +- breachsense/1.0.0/requirements.txt | 2 +- checkpoint/1.0.0/requirements.txt | 2 +- databasemanager/1.0.0/requirements.txt | 2 +- email/1.1.0/requirements.txt | 4 ++-- email/1.2.0/requirements.txt | 2 +- gitguardian/1.0.0/requirements.txt | 2 +- google-chat/1.0.0/requirements.txt | 2 +- gpg-tools/1.0.0/requirements.txt | 2 +- gpg-tools/1.1.0/requirements.txt | 2 +- gws/1.0.0/requirements.txt | 2 +- harfanglab-edr/1.0.0/requirements.txt | 2 +- http/1.1.0/requirements.txt | 2 +- http/1.2.0/requirements.txt | 2 +- mysql/1.0.0/requirements.txt | 2 +- netcraft/1.0.0/requirements.txt | 2 +- outlook-exchange/1.0.0/requirements.txt | 2 +- rss/1.0.0/requirements.txt | 2 +- secureworks/1.0.0/requirements.txt | 2 +- shuffle-subflow/1.0.0/requirements.txt | 2 +- shuffle-subflow/1.1.0/requirements.txt | 2 +- shuffle-tools/1.0.0/requirements.txt | 2 +- shuffle-tools/1.1.0/requirements.txt | 2 +- shuffle-tools/1.2.0/requirements.txt | 2 +- siemonster/1.0.0/requirements.txt | 2 +- sigma/1.0.0/requirements.txt | 2 +- snort3/1.0.0/requirements.txt | 2 +- sooty/1.0.0/requirements.txt | 2 +- twilio/1.9.0/requirements.txt | 2 +- unsupported/DuoSecurity/1.0.0/requirements.txt | 2 +- unsupported/ad-ldap/1.0.0/requirements.txt | 2 +- unsupported/cylance/1.0.0/requirements.txt | 2 +- unsupported/email-analyzer/1.0.0/requirements.txt | 2 +- unsupported/hoxhunt/1.0.0/requirements.txt | 2 +- unsupported/lastline/1.0.0/requirements.txt | 2 +- unsupported/microsoft-compliance/1.0.0/requirements.txt | 2 +- .../microsoft-identity-and-access/1.0.0/requirements.txt | 2 +- unsupported/microsoft-intune/1.0.0/requirements.txt | 2 +- .../microsoft-security-and-compliance/1.0.0/requirements.txt | 2 +- unsupported/microsoft-security-oauth2/1.0.0/requirements.txt | 2 +- .../microsoft-teams-system-access/1.0.0/requirements.txt | 2 +- unsupported/microsoft-teams/1.0.0/requirements.txt | 2 +- unsupported/misp/1.0.0/requirements.txt | 2 +- unsupported/nlp/1.0.0/requirements.txt | 2 +- unsupported/office365mgmt/1.0.0/requirements.txt | 2 +- unsupported/passivetotal/1.0.0/requirements.txt | 2 +- unsupported/recordedfuture/1.0.0/requirements.txt | 2 +- unsupported/servicenow/1.0.0/requirements.txt | 2 +- unsupported/shuffle-subflow/1.0.0/requirements.txt | 2 +- unsupported/splunk/1.0.0/requirements.txt | 2 +- unsupported/testing/1.0.0/requirements.txt | 2 +- unsupported/thehive/1.0.0/requirements.txt | 2 +- unsupported/thehive/1.1.0/requirements.txt | 2 +- unsupported/thehive/1.1.1/requirements.txt | 2 +- unsupported/thehive/1.1.2/requirements.txt | 2 +- unsupported/thehive/1.1.3/requirements.txt | 2 +- unsupported/twitter/1.0.0/requirements.txt | 2 +- unsupported/vulndb/1.0.0/requirements.txt | 2 +- yara/1.0.0/requirements.txt | 2 +- 68 files changed, 69 insertions(+), 69 deletions(-) diff --git a/active-directory/1.0.0/requirements.txt b/active-directory/1.0.0/requirements.txt index f7adbca6..53f3e6a1 100644 --- a/active-directory/1.0.0/requirements.txt +++ b/active-directory/1.0.0/requirements.txt @@ -1,2 +1,2 @@ ldap3==2.9.1 -requests==2.32.2 +requests==2.32.4 diff --git a/archive-org/1.0.0/requirements.txt b/archive-org/1.0.0/requirements.txt index bfb7d916..95feca5f 100644 --- a/archive-org/1.0.0/requirements.txt +++ b/archive-org/1.0.0/requirements.txt @@ -1,2 +1,2 @@ -requests==2.32.2 +requests==2.32.4 savepagenow==1.1.1 \ No newline at end of file diff --git a/archive-today/1.0.0/requirements.txt b/archive-today/1.0.0/requirements.txt index 96e17b90..1f5f6ba3 100644 --- a/archive-today/1.0.0/requirements.txt +++ b/archive-today/1.0.0/requirements.txt @@ -1,2 +1,2 @@ -requests==2.32.2 +requests==2.32.4 archiveis==0.0.9 \ No newline at end of file diff --git a/aws-cloudwatch/1.0.0/requirements.txt b/aws-cloudwatch/1.0.0/requirements.txt index 6801915d..f41ce43e 100644 --- a/aws-cloudwatch/1.0.0/requirements.txt +++ b/aws-cloudwatch/1.0.0/requirements.txt @@ -1,2 +1,2 @@ boto3==1.20.20 -requests==2.32.2 +requests==2.32.4 diff --git a/aws-dynamodb/1.0.0/requirements.txt b/aws-dynamodb/1.0.0/requirements.txt index 94f0fa1e..f66253b2 100644 --- a/aws-dynamodb/1.0.0/requirements.txt +++ b/aws-dynamodb/1.0.0/requirements.txt @@ -1,3 +1,3 @@ boto3==1.16.59 bson==0.5.10 -requests==2.32.2 \ No newline at end of file +requests==2.32.4 \ No newline at end of file diff --git a/aws-guardduty/1.0.0/requirements.txt b/aws-guardduty/1.0.0/requirements.txt index 48573434..423ace70 100644 --- a/aws-guardduty/1.0.0/requirements.txt +++ b/aws-guardduty/1.0.0/requirements.txt @@ -1,2 +1,2 @@ boto3==1.16.59 -requests==2.32.2 +requests==2.32.4 diff --git a/aws-iam/1.0.0/requirements.txt b/aws-iam/1.0.0/requirements.txt index c6e7e365..e2a84f1d 100644 --- a/aws-iam/1.0.0/requirements.txt +++ b/aws-iam/1.0.0/requirements.txt @@ -1,2 +1,2 @@ boto3==1.16.59 -requests==2.32.2 \ No newline at end of file +requests==2.32.4 \ No newline at end of file diff --git a/aws-lambda/1.0.0/requirements.txt b/aws-lambda/1.0.0/requirements.txt index 48573434..423ace70 100644 --- a/aws-lambda/1.0.0/requirements.txt +++ b/aws-lambda/1.0.0/requirements.txt @@ -1,2 +1,2 @@ boto3==1.16.59 -requests==2.32.2 +requests==2.32.4 diff --git a/aws-securityhub/1.0.0/requirements.txt b/aws-securityhub/1.0.0/requirements.txt index c6e7e365..e2a84f1d 100644 --- a/aws-securityhub/1.0.0/requirements.txt +++ b/aws-securityhub/1.0.0/requirements.txt @@ -1,2 +1,2 @@ boto3==1.16.59 -requests==2.32.2 \ No newline at end of file +requests==2.32.4 \ No newline at end of file diff --git a/aws-ses/1.0.0/requirements.txt b/aws-ses/1.0.0/requirements.txt index acb2f534..216ec100 100644 --- a/aws-ses/1.0.0/requirements.txt +++ b/aws-ses/1.0.0/requirements.txt @@ -1,2 +1,2 @@ -requests==2.32.2 +requests==2.32.4 boto3==1.16.59 \ No newline at end of file diff --git a/breachsense/1.0.0/requirements.txt b/breachsense/1.0.0/requirements.txt index 6b1425c8..480d0c4b 100644 --- a/breachsense/1.0.0/requirements.txt +++ b/breachsense/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.32.2 \ No newline at end of file +requests==2.32.4 \ No newline at end of file diff --git a/checkpoint/1.0.0/requirements.txt b/checkpoint/1.0.0/requirements.txt index 6b1425c8..480d0c4b 100644 --- a/checkpoint/1.0.0/requirements.txt +++ b/checkpoint/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.32.2 \ No newline at end of file +requests==2.32.4 \ No newline at end of file diff --git a/databasemanager/1.0.0/requirements.txt b/databasemanager/1.0.0/requirements.txt index bc69c45e..3edf3f44 100644 --- a/databasemanager/1.0.0/requirements.txt +++ b/databasemanager/1.0.0/requirements.txt @@ -1,3 +1,3 @@ -requests==2.32.2 +requests==2.32.4 mysql-connector-python==9.1.0 diff --git a/email/1.1.0/requirements.txt b/email/1.1.0/requirements.txt index 74f24329..bf8b6d67 100644 --- a/email/1.1.0/requirements.txt +++ b/email/1.1.0/requirements.txt @@ -1,6 +1,6 @@ -requests==2.32.2 +requests==2.32.4 glom==20.11.0 -requests==2.32.2 +requests==2.32.4 eml-parser==1.17.0 msg-parser==1.2.0 mail-parser==3.15.0 diff --git a/email/1.2.0/requirements.txt b/email/1.2.0/requirements.txt index 92f3feaf..0339123c 100644 --- a/email/1.2.0/requirements.txt +++ b/email/1.2.0/requirements.txt @@ -1,4 +1,4 @@ -requests==2.32.2 +requests==2.32.4 glom==20.11.0 eml-parser==1.17.5 msg-parser==1.2.0 diff --git a/gitguardian/1.0.0/requirements.txt b/gitguardian/1.0.0/requirements.txt index f84e6f34..ab60beb1 100644 --- a/gitguardian/1.0.0/requirements.txt +++ b/gitguardian/1.0.0/requirements.txt @@ -1,2 +1,2 @@ -requests==2.32.2 +requests==2.32.4 pygitguardian==1.1.2 \ No newline at end of file diff --git a/google-chat/1.0.0/requirements.txt b/google-chat/1.0.0/requirements.txt index 6e421681..bd6f2345 100644 --- a/google-chat/1.0.0/requirements.txt +++ b/google-chat/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.32.2 +requests==2.32.4 diff --git a/gpg-tools/1.0.0/requirements.txt b/gpg-tools/1.0.0/requirements.txt index b0258787..1edb807d 100644 --- a/gpg-tools/1.0.0/requirements.txt +++ b/gpg-tools/1.0.0/requirements.txt @@ -1,2 +1,2 @@ python-gnupg==0.4.6 -requests==2.32.2 \ No newline at end of file +requests==2.32.4 \ No newline at end of file diff --git a/gpg-tools/1.1.0/requirements.txt b/gpg-tools/1.1.0/requirements.txt index b0258787..1edb807d 100644 --- a/gpg-tools/1.1.0/requirements.txt +++ b/gpg-tools/1.1.0/requirements.txt @@ -1,2 +1,2 @@ python-gnupg==0.4.6 -requests==2.32.2 \ No newline at end of file +requests==2.32.4 \ No newline at end of file diff --git a/gws/1.0.0/requirements.txt b/gws/1.0.0/requirements.txt index 3a3758d1..a3b996d6 100644 --- a/gws/1.0.0/requirements.txt +++ b/gws/1.0.0/requirements.txt @@ -1,4 +1,4 @@ -requests==2.32.2 +requests==2.32.4 google-auth==1.28.0 google-auth-oauthlib==0.4.3 google-auth-httplib2==0.0.4 diff --git a/harfanglab-edr/1.0.0/requirements.txt b/harfanglab-edr/1.0.0/requirements.txt index 0e8b805f..b26fe9ce 100644 --- a/harfanglab-edr/1.0.0/requirements.txt +++ b/harfanglab-edr/1.0.0/requirements.txt @@ -1,4 +1,4 @@ -requests==2.32.2 +requests==2.32.4 python-dateutil==2.8.2 DateTime==4.7 Markdown==3.4.1 diff --git a/http/1.1.0/requirements.txt b/http/1.1.0/requirements.txt index 44c1e933..923ac71f 100644 --- a/http/1.1.0/requirements.txt +++ b/http/1.1.0/requirements.txt @@ -1,2 +1,2 @@ uncurl==0.0.10 -requests==2.32.2 \ No newline at end of file +requests==2.32.4 \ No newline at end of file diff --git a/http/1.2.0/requirements.txt b/http/1.2.0/requirements.txt index 44c1e933..923ac71f 100644 --- a/http/1.2.0/requirements.txt +++ b/http/1.2.0/requirements.txt @@ -1,2 +1,2 @@ uncurl==0.0.10 -requests==2.32.2 \ No newline at end of file +requests==2.32.4 \ No newline at end of file diff --git a/mysql/1.0.0/requirements.txt b/mysql/1.0.0/requirements.txt index 7fed93df..9ae049c1 100644 --- a/mysql/1.0.0/requirements.txt +++ b/mysql/1.0.0/requirements.txt @@ -1,2 +1,2 @@ -requests==2.32.2 +requests==2.32.4 mysql-connector-python==9.1.0 diff --git a/netcraft/1.0.0/requirements.txt b/netcraft/1.0.0/requirements.txt index 6b1425c8..480d0c4b 100644 --- a/netcraft/1.0.0/requirements.txt +++ b/netcraft/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.32.2 \ No newline at end of file +requests==2.32.4 \ No newline at end of file diff --git a/outlook-exchange/1.0.0/requirements.txt b/outlook-exchange/1.0.0/requirements.txt index d7abbf35..c7a756d5 100644 --- a/outlook-exchange/1.0.0/requirements.txt +++ b/outlook-exchange/1.0.0/requirements.txt @@ -2,4 +2,4 @@ cryptography==44.0.1 exchangelib==3.3.2 eml_parser==1.14.4 glom==20.11.0 -requests==2.32.2 +requests==2.32.4 diff --git a/rss/1.0.0/requirements.txt b/rss/1.0.0/requirements.txt index 3cd0791a..1c09069e 100644 --- a/rss/1.0.0/requirements.txt +++ b/rss/1.0.0/requirements.txt @@ -1,2 +1,2 @@ feedparser==6.0.8 -requests==2.32.2 +requests==2.32.4 diff --git a/secureworks/1.0.0/requirements.txt b/secureworks/1.0.0/requirements.txt index 6b1425c8..480d0c4b 100644 --- a/secureworks/1.0.0/requirements.txt +++ b/secureworks/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.32.2 \ No newline at end of file +requests==2.32.4 \ No newline at end of file diff --git a/shuffle-subflow/1.0.0/requirements.txt b/shuffle-subflow/1.0.0/requirements.txt index 6b1425c8..480d0c4b 100644 --- a/shuffle-subflow/1.0.0/requirements.txt +++ b/shuffle-subflow/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.32.2 \ No newline at end of file +requests==2.32.4 \ No newline at end of file diff --git a/shuffle-subflow/1.1.0/requirements.txt b/shuffle-subflow/1.1.0/requirements.txt index 6b1425c8..480d0c4b 100644 --- a/shuffle-subflow/1.1.0/requirements.txt +++ b/shuffle-subflow/1.1.0/requirements.txt @@ -1 +1 @@ -requests==2.32.2 \ No newline at end of file +requests==2.32.4 \ No newline at end of file diff --git a/shuffle-tools/1.0.0/requirements.txt b/shuffle-tools/1.0.0/requirements.txt index 5e4e9e11..cd48a310 100644 --- a/shuffle-tools/1.0.0/requirements.txt +++ b/shuffle-tools/1.0.0/requirements.txt @@ -2,7 +2,7 @@ ioc_finder==6.0.1 py7zr==0.20.2 rarfile==4.0 pyminizip==0.2.6 -requests==2.32.2 +requests==2.32.4 xmltodict==0.11.0 json2xml==5.0.5 ipaddress==1.0.23 diff --git a/shuffle-tools/1.1.0/requirements.txt b/shuffle-tools/1.1.0/requirements.txt index 1008589f..cd48a310 100644 --- a/shuffle-tools/1.1.0/requirements.txt +++ b/shuffle-tools/1.1.0/requirements.txt @@ -2,7 +2,7 @@ ioc_finder==6.0.1 py7zr==0.20.2 rarfile==4.0 pyminizip==0.2.6 -requests==2.32.3 +requests==2.32.4 xmltodict==0.11.0 json2xml==5.0.5 ipaddress==1.0.23 diff --git a/shuffle-tools/1.2.0/requirements.txt b/shuffle-tools/1.2.0/requirements.txt index 4174193b..89ca3320 100644 --- a/shuffle-tools/1.2.0/requirements.txt +++ b/shuffle-tools/1.2.0/requirements.txt @@ -2,7 +2,7 @@ ioc_finder==7.3.0 py7zr==0.22.0 rarfile==4.2 pyminizip==0.2.6 -requests==2.32.3 +requests==2.32.4 xmltodict==0.14.2 json2xml==5.0.5 ipaddress==1.0.23 diff --git a/siemonster/1.0.0/requirements.txt b/siemonster/1.0.0/requirements.txt index 6b1425c8..480d0c4b 100644 --- a/siemonster/1.0.0/requirements.txt +++ b/siemonster/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.32.2 \ No newline at end of file +requests==2.32.4 \ No newline at end of file diff --git a/sigma/1.0.0/requirements.txt b/sigma/1.0.0/requirements.txt index 11fb6d4c..755761ce 100644 --- a/sigma/1.0.0/requirements.txt +++ b/sigma/1.0.0/requirements.txt @@ -1,2 +1,2 @@ -requests==2.32.2 +requests==2.32.4 sigmatools==0.20 diff --git a/snort3/1.0.0/requirements.txt b/snort3/1.0.0/requirements.txt index 63e7be78..090113b4 100644 --- a/snort3/1.0.0/requirements.txt +++ b/snort3/1.0.0/requirements.txt @@ -1,2 +1,2 @@ # No extra requirements needed -requests==2.32.2 \ No newline at end of file +requests==2.32.4 \ No newline at end of file diff --git a/sooty/1.0.0/requirements.txt b/sooty/1.0.0/requirements.txt index e806671e..0ade4fc4 100644 --- a/sooty/1.0.0/requirements.txt +++ b/sooty/1.0.0/requirements.txt @@ -1,2 +1,2 @@ -requests==2.32.2 +requests==2.32.4 ipwhois==1.2.0 \ No newline at end of file diff --git a/twilio/1.9.0/requirements.txt b/twilio/1.9.0/requirements.txt index 6b1425c8..480d0c4b 100644 --- a/twilio/1.9.0/requirements.txt +++ b/twilio/1.9.0/requirements.txt @@ -1 +1 @@ -requests==2.32.2 \ No newline at end of file +requests==2.32.4 \ No newline at end of file diff --git a/unsupported/DuoSecurity/1.0.0/requirements.txt b/unsupported/DuoSecurity/1.0.0/requirements.txt index 6b1425c8..480d0c4b 100644 --- a/unsupported/DuoSecurity/1.0.0/requirements.txt +++ b/unsupported/DuoSecurity/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.32.2 \ No newline at end of file +requests==2.32.4 \ No newline at end of file diff --git a/unsupported/ad-ldap/1.0.0/requirements.txt b/unsupported/ad-ldap/1.0.0/requirements.txt index f7adbca6..53f3e6a1 100644 --- a/unsupported/ad-ldap/1.0.0/requirements.txt +++ b/unsupported/ad-ldap/1.0.0/requirements.txt @@ -1,2 +1,2 @@ ldap3==2.9.1 -requests==2.32.2 +requests==2.32.4 diff --git a/unsupported/cylance/1.0.0/requirements.txt b/unsupported/cylance/1.0.0/requirements.txt index 32286ae4..a76afb96 100644 --- a/unsupported/cylance/1.0.0/requirements.txt +++ b/unsupported/cylance/1.0.0/requirements.txt @@ -1,3 +1,3 @@ cryptography==44.0.1 -requests==2.32.2 +requests==2.32.4 PyJWT==2.4.0 diff --git a/unsupported/email-analyzer/1.0.0/requirements.txt b/unsupported/email-analyzer/1.0.0/requirements.txt index 9b077d42..f3ecd49c 100644 --- a/unsupported/email-analyzer/1.0.0/requirements.txt +++ b/unsupported/email-analyzer/1.0.0/requirements.txt @@ -1,4 +1,4 @@ -requests==2.32.2 +requests==2.32.4 eml-parser==1.14.7 msg-parser==1.2.0 mail-parser==3.15.0 diff --git a/unsupported/hoxhunt/1.0.0/requirements.txt b/unsupported/hoxhunt/1.0.0/requirements.txt index 6b1425c8..480d0c4b 100644 --- a/unsupported/hoxhunt/1.0.0/requirements.txt +++ b/unsupported/hoxhunt/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.32.2 \ No newline at end of file +requests==2.32.4 \ No newline at end of file diff --git a/unsupported/lastline/1.0.0/requirements.txt b/unsupported/lastline/1.0.0/requirements.txt index 6b1425c8..480d0c4b 100644 --- a/unsupported/lastline/1.0.0/requirements.txt +++ b/unsupported/lastline/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.32.2 \ No newline at end of file +requests==2.32.4 \ No newline at end of file diff --git a/unsupported/microsoft-compliance/1.0.0/requirements.txt b/unsupported/microsoft-compliance/1.0.0/requirements.txt index 6e421681..bd6f2345 100644 --- a/unsupported/microsoft-compliance/1.0.0/requirements.txt +++ b/unsupported/microsoft-compliance/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.32.2 +requests==2.32.4 diff --git a/unsupported/microsoft-identity-and-access/1.0.0/requirements.txt b/unsupported/microsoft-identity-and-access/1.0.0/requirements.txt index 6b1425c8..480d0c4b 100644 --- a/unsupported/microsoft-identity-and-access/1.0.0/requirements.txt +++ b/unsupported/microsoft-identity-and-access/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.32.2 \ No newline at end of file +requests==2.32.4 \ No newline at end of file diff --git a/unsupported/microsoft-intune/1.0.0/requirements.txt b/unsupported/microsoft-intune/1.0.0/requirements.txt index 6b1425c8..480d0c4b 100644 --- a/unsupported/microsoft-intune/1.0.0/requirements.txt +++ b/unsupported/microsoft-intune/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.32.2 \ No newline at end of file +requests==2.32.4 \ No newline at end of file diff --git a/unsupported/microsoft-security-and-compliance/1.0.0/requirements.txt b/unsupported/microsoft-security-and-compliance/1.0.0/requirements.txt index 6b1425c8..480d0c4b 100644 --- a/unsupported/microsoft-security-and-compliance/1.0.0/requirements.txt +++ b/unsupported/microsoft-security-and-compliance/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.32.2 \ No newline at end of file +requests==2.32.4 \ No newline at end of file diff --git a/unsupported/microsoft-security-oauth2/1.0.0/requirements.txt b/unsupported/microsoft-security-oauth2/1.0.0/requirements.txt index 6e421681..bd6f2345 100644 --- a/unsupported/microsoft-security-oauth2/1.0.0/requirements.txt +++ b/unsupported/microsoft-security-oauth2/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.32.2 +requests==2.32.4 diff --git a/unsupported/microsoft-teams-system-access/1.0.0/requirements.txt b/unsupported/microsoft-teams-system-access/1.0.0/requirements.txt index 6b1425c8..480d0c4b 100644 --- a/unsupported/microsoft-teams-system-access/1.0.0/requirements.txt +++ b/unsupported/microsoft-teams-system-access/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.32.2 \ No newline at end of file +requests==2.32.4 \ No newline at end of file diff --git a/unsupported/microsoft-teams/1.0.0/requirements.txt b/unsupported/microsoft-teams/1.0.0/requirements.txt index 6b1425c8..480d0c4b 100644 --- a/unsupported/microsoft-teams/1.0.0/requirements.txt +++ b/unsupported/microsoft-teams/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.32.2 \ No newline at end of file +requests==2.32.4 \ No newline at end of file diff --git a/unsupported/misp/1.0.0/requirements.txt b/unsupported/misp/1.0.0/requirements.txt index 6b1425c8..480d0c4b 100644 --- a/unsupported/misp/1.0.0/requirements.txt +++ b/unsupported/misp/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.32.2 \ No newline at end of file +requests==2.32.4 \ No newline at end of file diff --git a/unsupported/nlp/1.0.0/requirements.txt b/unsupported/nlp/1.0.0/requirements.txt index 64c303c4..43e5970b 100644 --- a/unsupported/nlp/1.0.0/requirements.txt +++ b/unsupported/nlp/1.0.0/requirements.txt @@ -1,4 +1,4 @@ cyberspacy==1.1.1 tika==1.24 -requests==2.32.2 +requests==2.32.4 spacy==2.3.5 diff --git a/unsupported/office365mgmt/1.0.0/requirements.txt b/unsupported/office365mgmt/1.0.0/requirements.txt index b086d104..73f08b38 100644 --- a/unsupported/office365mgmt/1.0.0/requirements.txt +++ b/unsupported/office365mgmt/1.0.0/requirements.txt @@ -1,2 +1,2 @@ # No extra requirements needed -requests==2.32.2 +requests==2.32.4 diff --git a/unsupported/passivetotal/1.0.0/requirements.txt b/unsupported/passivetotal/1.0.0/requirements.txt index 6b1425c8..480d0c4b 100644 --- a/unsupported/passivetotal/1.0.0/requirements.txt +++ b/unsupported/passivetotal/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.32.2 \ No newline at end of file +requests==2.32.4 \ No newline at end of file diff --git a/unsupported/recordedfuture/1.0.0/requirements.txt b/unsupported/recordedfuture/1.0.0/requirements.txt index 6b1425c8..480d0c4b 100644 --- a/unsupported/recordedfuture/1.0.0/requirements.txt +++ b/unsupported/recordedfuture/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.32.2 \ No newline at end of file +requests==2.32.4 \ No newline at end of file diff --git a/unsupported/servicenow/1.0.0/requirements.txt b/unsupported/servicenow/1.0.0/requirements.txt index 6b1425c8..480d0c4b 100644 --- a/unsupported/servicenow/1.0.0/requirements.txt +++ b/unsupported/servicenow/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.32.2 \ No newline at end of file +requests==2.32.4 \ No newline at end of file diff --git a/unsupported/shuffle-subflow/1.0.0/requirements.txt b/unsupported/shuffle-subflow/1.0.0/requirements.txt index 6b1425c8..480d0c4b 100644 --- a/unsupported/shuffle-subflow/1.0.0/requirements.txt +++ b/unsupported/shuffle-subflow/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.32.2 \ No newline at end of file +requests==2.32.4 \ No newline at end of file diff --git a/unsupported/splunk/1.0.0/requirements.txt b/unsupported/splunk/1.0.0/requirements.txt index 46ff3c1b..bb76b456 100644 --- a/unsupported/splunk/1.0.0/requirements.txt +++ b/unsupported/splunk/1.0.0/requirements.txt @@ -1,2 +1,2 @@ python-magic==0.4.18 -requests==2.32.2 \ No newline at end of file +requests==2.32.4 \ No newline at end of file diff --git a/unsupported/testing/1.0.0/requirements.txt b/unsupported/testing/1.0.0/requirements.txt index 6b1425c8..480d0c4b 100644 --- a/unsupported/testing/1.0.0/requirements.txt +++ b/unsupported/testing/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.32.2 \ No newline at end of file +requests==2.32.4 \ No newline at end of file diff --git a/unsupported/thehive/1.0.0/requirements.txt b/unsupported/thehive/1.0.0/requirements.txt index 916d3b0d..175d3de0 100644 --- a/unsupported/thehive/1.0.0/requirements.txt +++ b/unsupported/thehive/1.0.0/requirements.txt @@ -1,3 +1,3 @@ -requests==2.32.2 +requests==2.32.4 thehive4py==1.8.1 python-magic==0.4.18 diff --git a/unsupported/thehive/1.1.0/requirements.txt b/unsupported/thehive/1.1.0/requirements.txt index 916d3b0d..175d3de0 100644 --- a/unsupported/thehive/1.1.0/requirements.txt +++ b/unsupported/thehive/1.1.0/requirements.txt @@ -1,3 +1,3 @@ -requests==2.32.2 +requests==2.32.4 thehive4py==1.8.1 python-magic==0.4.18 diff --git a/unsupported/thehive/1.1.1/requirements.txt b/unsupported/thehive/1.1.1/requirements.txt index 916d3b0d..175d3de0 100644 --- a/unsupported/thehive/1.1.1/requirements.txt +++ b/unsupported/thehive/1.1.1/requirements.txt @@ -1,3 +1,3 @@ -requests==2.32.2 +requests==2.32.4 thehive4py==1.8.1 python-magic==0.4.18 diff --git a/unsupported/thehive/1.1.2/requirements.txt b/unsupported/thehive/1.1.2/requirements.txt index 916d3b0d..175d3de0 100644 --- a/unsupported/thehive/1.1.2/requirements.txt +++ b/unsupported/thehive/1.1.2/requirements.txt @@ -1,3 +1,3 @@ -requests==2.32.2 +requests==2.32.4 thehive4py==1.8.1 python-magic==0.4.18 diff --git a/unsupported/thehive/1.1.3/requirements.txt b/unsupported/thehive/1.1.3/requirements.txt index 916d3b0d..175d3de0 100644 --- a/unsupported/thehive/1.1.3/requirements.txt +++ b/unsupported/thehive/1.1.3/requirements.txt @@ -1,3 +1,3 @@ -requests==2.32.2 +requests==2.32.4 thehive4py==1.8.1 python-magic==0.4.18 diff --git a/unsupported/twitter/1.0.0/requirements.txt b/unsupported/twitter/1.0.0/requirements.txt index f7c96133..5d086241 100644 --- a/unsupported/twitter/1.0.0/requirements.txt +++ b/unsupported/twitter/1.0.0/requirements.txt @@ -1,2 +1,2 @@ -requests==2.32.2 +requests==2.32.4 twython==3.9.1 diff --git a/unsupported/vulndb/1.0.0/requirements.txt b/unsupported/vulndb/1.0.0/requirements.txt index 6e421681..bd6f2345 100644 --- a/unsupported/vulndb/1.0.0/requirements.txt +++ b/unsupported/vulndb/1.0.0/requirements.txt @@ -1 +1 @@ -requests==2.32.2 +requests==2.32.4 diff --git a/yara/1.0.0/requirements.txt b/yara/1.0.0/requirements.txt index b086d104..73f08b38 100644 --- a/yara/1.0.0/requirements.txt +++ b/yara/1.0.0/requirements.txt @@ -1,2 +1,2 @@ # No extra requirements needed -requests==2.32.2 +requests==2.32.4 From f918aa93db1c8e14ca6dcf7a7e1154573f8a2ce4 Mon Sep 17 00:00:00 2001 From: yashsinghcodes Date: Tue, 10 Jun 2025 17:00:50 +0530 Subject: [PATCH 217/259] new postgress python app --- postgress/1.0.0/Dockerfile | 27 ++++++++++++++++ postgress/1.0.0/README.md | 2 ++ postgress/1.0.0/api.yaml | 55 ++++++++++++++++++++++++++++++++ postgress/1.0.0/requirements.txt | 2 ++ postgress/1.0.0/src/app.py | 35 ++++++++++++++++++++ 5 files changed, 121 insertions(+) create mode 100644 postgress/1.0.0/Dockerfile create mode 100644 postgress/1.0.0/README.md create mode 100644 postgress/1.0.0/api.yaml create mode 100644 postgress/1.0.0/requirements.txt create mode 100644 postgress/1.0.0/src/app.py diff --git a/postgress/1.0.0/Dockerfile b/postgress/1.0.0/Dockerfile new file mode 100644 index 00000000..41f976bb --- /dev/null +++ b/postgress/1.0.0/Dockerfile @@ -0,0 +1,27 @@ +# Base our app image off of the WALKOFF App SDK image +FROM frikky/shuffle:app_sdk as base + +# We're going to stage away all of the bloat from the build tools so lets create a builder stage +FROM base as builder + +# Install all alpine build tools needed for our pip installs +RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev + +# Install all of our pip packages in a single directory that we can copy to our base image later +RUN mkdir /install +WORKDIR /install +COPY requirements.txt /requirements.txt +RUN pip install --prefix="/install" -r /requirements.txt + +# Switch back to our base image and copy in all of our built packages and source code +FROM base +COPY --from=builder /install /usr/local +COPY src /app + +# Install any binary dependencies needed in our final image +# RUN apk --no-cache add --update my_binary_dependency + + +# Finally, lets run our app! +WORKDIR /app +CMD python app.py --log-level DEBUG diff --git a/postgress/1.0.0/README.md b/postgress/1.0.0/README.md new file mode 100644 index 00000000..12b051c2 --- /dev/null +++ b/postgress/1.0.0/README.md @@ -0,0 +1,2 @@ +# PostgreSQL Shuffle App +This app connects to PostgreSQL and executes queries. diff --git a/postgress/1.0.0/api.yaml b/postgress/1.0.0/api.yaml new file mode 100644 index 00000000..26467487 --- /dev/null +++ b/postgress/1.0.0/api.yaml @@ -0,0 +1,55 @@ +app_version: 1.0.0 +name: postgress +description: postgress integration. Compatible with SQL databases. +contact_info: + name: "@d4rkw0lv3s" + url: https://github.com/D4rkw0lv3s + email: d4rkw0lv3s@outlook.pt +tags: + - postgress +categories: + - Intel + - Network +actions: + - name: run_query + description: Create a new database + parameters: + - name: host + description: mysql server ip or fqdn + example: "myserver.com or 127.0.0.1" + required: true + schema: + type: string + - name: port + description: mysql database + example: "my_database" + required: false + schema: + type: string + - name: dbname + description: mysql database + example: "my_database" + required: false + schema: + type: string + - name: user + description: mysql database + example: "my_database" + required: false + schema: + type: string + - name: password + description: mysql database + example: "my_database" + required: false + schema: + type: string + - name: query + description: mysql database + example: "my_database" + required: false + schema: + type: string + return: + schema: + type: string diff --git a/postgress/1.0.0/requirements.txt b/postgress/1.0.0/requirements.txt new file mode 100644 index 00000000..78f864b2 --- /dev/null +++ b/postgress/1.0.0/requirements.txt @@ -0,0 +1,2 @@ +psycopg2-binary +shuffle-sdk diff --git a/postgress/1.0.0/src/app.py b/postgress/1.0.0/src/app.py new file mode 100644 index 00000000..3c973e49 --- /dev/null +++ b/postgress/1.0.0/src/app.py @@ -0,0 +1,35 @@ +import psycopg2 +from psycopg2.extras import RealDictCursor +#from walkoff_app_sdk.app_base import AppBase +from shuffle_sdk import AppBase + + +class PostgreSQL(AppBase): + __version__ = "1.0.0" + app_name = "PostgreSQL" + + def __init__(self, redis, logger, console_logger=None): + super().__init__(redis, logger, console_logger) + + def connect(self, host, port, dbname, user, password): + conn = psycopg2.connect( + host=host, + port=port, + dbname=dbname, + user=user, + password=password, + cursor_factory=RealDictCursor + ) + return conn + + def run_query(self, host, port, dbname, user, password, query): + with self.connect(host, port, dbname, user, password) as conn: + with conn.cursor() as cur: + cur.execute(query) + try: + return {"result": cur.fetchall()} + except psycopg2.ProgrammingError: + return {"message": "Query executed successfully, no data returned."} + +if __name__ == "__main__": + PostgreSQL.run() From efafb3f11eb132d11f9d6255d57340e802407647 Mon Sep 17 00:00:00 2001 From: yashsinghcodes Date: Tue, 10 Jun 2025 18:04:31 +0530 Subject: [PATCH 218/259] added try-catch in output and error_log --- shuffle-tools/1.2.0/src/app.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index ecdf1630..78e84860 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -2632,11 +2632,19 @@ def run_ssh_command(self, host, port, user_name, private_key_file_id, password, try: stdin, stdout, stderr = ssh_client.exec_command(str(command)) + try: + errorLog = stderr.read().decode(errors='ignore') + except Exception as e: + errorLog = f"Failed to read stderr {e}" + try: + output = stdout.read().decode(errors='ignore') + except Exception as e: + output = f"Failed to read stdout {e}" except Exception as e: return {"success":"false","message":str(e)} - return {"success":"true","output": stdout.read().decode(errors='ignore'), "error_logs": stderr.read().decode(errors='ignore')} + return {"success":"true","output": output, "error_logs": errorLog} def cleanup_ioc_data(self, input_data): # Remove unecessary parts like { and }, quotes etc From 58b069bdd2e8276fceb36ea8b55f36868adc34a4 Mon Sep 17 00:00:00 2001 From: Frikky Date: Tue, 10 Jun 2025 16:44:22 +0200 Subject: [PATCH 219/259] Fixed user input multi region url problem --- http/1.0.0/requirements.txt | 3 ++- http/1.0.0/src/app.py | 2 +- shuffle-ai/1.0.0/src/app.py | 9 ++++++--- shuffle-subflow/1.1.0/src/app.py | 9 +++++++-- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/http/1.0.0/requirements.txt b/http/1.0.0/requirements.txt index 44c1e933..6753e935 100644 --- a/http/1.0.0/requirements.txt +++ b/http/1.0.0/requirements.txt @@ -1,2 +1,3 @@ uncurl==0.0.10 -requests==2.32.2 \ No newline at end of file +requests==2.32.2 +shuffle-sdk==0.0.26 diff --git a/http/1.0.0/src/app.py b/http/1.0.0/src/app.py index af48e5a8..8b4f2218 100755 --- a/http/1.0.0/src/app.py +++ b/http/1.0.0/src/app.py @@ -8,7 +8,7 @@ import requests import subprocess -from walkoff_app_sdk.app_base import AppBase +from shuffle_sdk import AppBase class HTTP(AppBase): __version__ = "1.0.0" diff --git a/shuffle-ai/1.0.0/src/app.py b/shuffle-ai/1.0.0/src/app.py index f66e01fd..eb025b40 100644 --- a/shuffle-ai/1.0.0/src/app.py +++ b/shuffle-ai/1.0.0/src/app.py @@ -456,8 +456,6 @@ def run_schemaless(self, category, action, app_name="", fields=""): if app_name: data["app_name"] = app_name - self.logger.info(f"\n\nFIELDS MAPPED\n\n: {fields}") - if fields: if isinstance(fields, list): data["fields"] = fields @@ -522,7 +520,12 @@ def run_schemaless(self, category, action, app_name="", fields=""): self.logger.info("[ERROR] Failed to get response headers (category action url debug mapping): %s" % e) try: - return request.json() + data = request.json() + + #if "success" in data and "result" in data and "errors" in data: + # return data["result"] + + return data except: return request.text diff --git a/shuffle-subflow/1.1.0/src/app.py b/shuffle-subflow/1.1.0/src/app.py index 9641cbd4..b48b8581 100644 --- a/shuffle-subflow/1.1.0/src/app.py +++ b/shuffle-subflow/1.1.0/src/app.py @@ -68,11 +68,16 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" if "shuffle-backend" in frontend_url: frontend_url = "" + api_continue_url = "%s/api/v1/workflows/%s/execute?authorization=%s&reference_execution=%s&answer=true&source_node=%s" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"], source_node) + api_abort_url = "%s/api/v1/workflows/%s/execute?authorization=%s&reference_execution=%s&answer=false&source_node=%s" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"], source_node) + + # Remove subdomain before .shuffler.io so that https://*.shuffler.io -> https://shuffler.io + if ".shuffler.io" in frontend_url: + frontend_url = "https://shuffler.io" + explore_path = "%s/forms/%s?authorization=%s&reference_execution=%s&source_node=%s" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"], source_node) frontend_continue_url = "%s/forms/%s?authorization=%s&reference_execution=%s&answer=true&source_node=%s" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"], source_node) frontend_abort_url = "%s/forms/%s?authorization=%s&reference_execution=%s&answer=false&source_node=%s" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"], source_node) - api_continue_url = "%s/api/v1/workflows/%s/execute?authorization=%s&reference_execution=%s&answer=true&source_node=%s" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"], source_node) - api_abort_url = "%s/api/v1/workflows/%s/execute?authorization=%s&reference_execution=%s&answer=false&source_node=%s" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"], source_node) result["links"]["frontend_no_answer"] = explore_path result["links"]["frontend_continue"] = frontend_continue_url From 696dbfb7abe2562d451928716e8bf8cd3637f45b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Jun 2025 15:59:26 +0000 Subject: [PATCH 220/259] Bump the pip group across 7 directories with 1 update Bumps the pip group with 1 update in the /aws-ec2/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /aws-s3/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /aws-waf/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /cortex/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /email/1.3.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /http/1.0.0 directory: [requests](https://github.com/psf/requests). Bumps the pip group with 1 update in the /unsupported/ansible/1.0.0 directory: [requests](https://github.com/psf/requests). Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.3 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.2 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) Updates `requests` from 2.32.3 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) --- updated-dependencies: - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip ... Signed-off-by: dependabot[bot] --- aws-ec2/1.0.0/requirements.txt | 2 +- aws-s3/1.0.0/requirements.txt | 2 +- aws-waf/1.0.0/requirements.txt | 2 +- cortex/1.0.0/requirements.txt | 2 +- email/1.3.0/requirements.txt | 2 +- http/1.0.0/requirements.txt | 2 +- unsupported/ansible/1.0.0/requirements.txt | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/aws-ec2/1.0.0/requirements.txt b/aws-ec2/1.0.0/requirements.txt index 6801915d..f41ce43e 100644 --- a/aws-ec2/1.0.0/requirements.txt +++ b/aws-ec2/1.0.0/requirements.txt @@ -1,2 +1,2 @@ boto3==1.20.20 -requests==2.32.2 +requests==2.32.4 diff --git a/aws-s3/1.0.0/requirements.txt b/aws-s3/1.0.0/requirements.txt index 7c03c548..8c127e25 100644 --- a/aws-s3/1.0.0/requirements.txt +++ b/aws-s3/1.0.0/requirements.txt @@ -1,3 +1,3 @@ boto3==1.16.59 bson==0.5.10 -requests==2.32.2 +requests==2.32.4 diff --git a/aws-waf/1.0.0/requirements.txt b/aws-waf/1.0.0/requirements.txt index 48573434..423ace70 100644 --- a/aws-waf/1.0.0/requirements.txt +++ b/aws-waf/1.0.0/requirements.txt @@ -1,2 +1,2 @@ boto3==1.16.59 -requests==2.32.2 +requests==2.32.4 diff --git a/cortex/1.0.0/requirements.txt b/cortex/1.0.0/requirements.txt index c1b6101c..fbca76ef 100644 --- a/cortex/1.0.0/requirements.txt +++ b/cortex/1.0.0/requirements.txt @@ -1,3 +1,3 @@ -requests==2.32.2 +requests==2.32.4 python-magic==0.4.18 cortex4py==2.0.1 diff --git a/email/1.3.0/requirements.txt b/email/1.3.0/requirements.txt index 90d0c96d..5d6b0d2c 100644 --- a/email/1.3.0/requirements.txt +++ b/email/1.3.0/requirements.txt @@ -1,4 +1,4 @@ -requests==2.32.3 +requests==2.32.4 glom==20.11.0 jsonpickle==2.0.0 diff --git a/http/1.0.0/requirements.txt b/http/1.0.0/requirements.txt index 6753e935..2774f2f0 100644 --- a/http/1.0.0/requirements.txt +++ b/http/1.0.0/requirements.txt @@ -1,3 +1,3 @@ uncurl==0.0.10 -requests==2.32.2 +requests==2.32.4 shuffle-sdk==0.0.26 diff --git a/unsupported/ansible/1.0.0/requirements.txt b/unsupported/ansible/1.0.0/requirements.txt index e1176e47..c67cded2 100644 --- a/unsupported/ansible/1.0.0/requirements.txt +++ b/unsupported/ansible/1.0.0/requirements.txt @@ -1,2 +1,2 @@ -requests==2.32.3 +requests==2.32.4 ansible==8.5.0 From 229aac0429d70af23a66cf8f2460d3e1e5029a08 Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 13 Jun 2025 14:07:22 +0200 Subject: [PATCH 221/259] Added backend_url for shuffle multi region --- shuffle-subflow/1.1.0/src/app.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shuffle-subflow/1.1.0/src/app.py b/shuffle-subflow/1.1.0/src/app.py index b48b8581..252e46e9 100644 --- a/shuffle-subflow/1.1.0/src/app.py +++ b/shuffle-subflow/1.1.0/src/app.py @@ -75,9 +75,9 @@ def run_userinput(self, user_apikey, sms="", email="", subflow="", information=" if ".shuffler.io" in frontend_url: frontend_url = "https://shuffler.io" - explore_path = "%s/forms/%s?authorization=%s&reference_execution=%s&source_node=%s" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"], source_node) - frontend_continue_url = "%s/forms/%s?authorization=%s&reference_execution=%s&answer=true&source_node=%s" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"], source_node) - frontend_abort_url = "%s/forms/%s?authorization=%s&reference_execution=%s&answer=false&source_node=%s" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"], source_node) + explore_path = "%s/forms/%s?authorization=%s&reference_execution=%s&source_node=%s&backend_url=%s" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"], source_node, backend_url) + frontend_continue_url = "%s/forms/%s?authorization=%s&reference_execution=%s&answer=true&source_node=%s&backend_url=%s" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"], source_node, backend_url) + frontend_abort_url = "%s/forms/%s?authorization=%s&reference_execution=%s&answer=false&source_node=%s&backend_url=%s" % (frontend_url, self.full_execution["workflow"]["id"], self.full_execution["authorization"], self.full_execution["execution_id"], source_node, backend_url) result["links"]["frontend_no_answer"] = explore_path result["links"]["frontend_continue"] = frontend_continue_url From a260c1d1b71cabf74239821e3a3aaa73c1819548 Mon Sep 17 00:00:00 2001 From: yashsinghcodes Date: Mon, 16 Jun 2025 16:04:38 +0530 Subject: [PATCH 222/259] rollback to request 2.32.3 --- email/1.3.0/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/email/1.3.0/requirements.txt b/email/1.3.0/requirements.txt index 5d6b0d2c..90d0c96d 100644 --- a/email/1.3.0/requirements.txt +++ b/email/1.3.0/requirements.txt @@ -1,4 +1,4 @@ -requests==2.32.4 +requests==2.32.3 glom==20.11.0 jsonpickle==2.0.0 From 62f6f2b65680db1e150af5b225b939c77aace5c8 Mon Sep 17 00:00:00 2001 From: yashsinghcodes Date: Thu, 19 Jun 2025 22:17:01 +0530 Subject: [PATCH 223/259] fix a small exception handling --- email/1.3.0/src/app.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/email/1.3.0/src/app.py b/email/1.3.0/src/app.py index aabb1bbe..049481f6 100644 --- a/email/1.3.0/src/app.py +++ b/email/1.3.0/src/app.py @@ -382,7 +382,11 @@ def merge(d1, d2): output_dict["imap_id"] = id_list[i] # Add message-id as top returned field - output_dict["message_id"] = parsed_eml["header"]["header"]["message-id"][0] + try: + output_dict["message_id"] = parsed_eml["header"]["header"]["message-id"][0] + except Exception as _: + output_dict["message_id"] = "" + if upload_email_shuffle: self.logger.info("Uploading email to shuffle") From 9ec5e3dc2fe28d6536a698d1fbdc14cd47743c93 Mon Sep 17 00:00:00 2001 From: wbatista Date: Fri, 20 Jun 2025 15:02:42 -0300 Subject: [PATCH 224/259] issue fix test --- shuffle-tools/1.2.0/src/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 78e84860..12389cc6 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -811,6 +811,7 @@ def filter_list(self, input_list, field, check, value, opposite): failed_list.append(item) elif check == "equals any of": + value = self.parse_list_internal(value) checklist = value.split(",") found = False for subcheck in checklist: From e922b4a935099cc8ecff45fe06762b730a5cd978 Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Sat, 5 Jul 2025 00:27:30 +0530 Subject: [PATCH 225/259] fix: final changes to make email app work --- email/1.3.0/src/app.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/email/1.3.0/src/app.py b/email/1.3.0/src/app.py index a233ce09..de58af53 100644 --- a/email/1.3.0/src/app.py +++ b/email/1.3.0/src/app.py @@ -67,9 +67,19 @@ def send_email_shuffle(self, apikey, recipients, subject, body): elif "," in recipients: targets = recipients.split(",") - data = {"targets": targets, "body": body, "subject": subject, "type": "alert", "email_app": True} + data = {"targets": targets, "body": body, "subject": subject, "type": "alert", "email_app": True, "reference_execution": self.current_execution_id} - url = "https://shuffler.io/functions/sendmail" + # url = "https://shuffler.io/functions/sendmail" + + url = "https://7692-2405-201-4019-f142-8124-2465-9b7d-4945.ngrok-free.app/functions/sendmail" + + print("apikey: ", apikey, " authorization: ", self.authorization) + + time.sleep(10) + + if apikey.strip() == "" and self.authorization != "standalone": + apikey = self.authorization + headers = {"Authorization": "Bearer %s" % apikey} return requests.post(url, headers=headers, json=data).text From da431d746e5b26348a0e494a41cc4e7a33c691ce Mon Sep 17 00:00:00 2001 From: Aditya <60684641+0x0elliot@users.noreply.github.com> Date: Thu, 10 Jul 2025 01:06:10 +0530 Subject: [PATCH 226/259] fix: finalising function --- email/1.3.0/src/app.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/email/1.3.0/src/app.py b/email/1.3.0/src/app.py index de58af53..ebe7702b 100644 --- a/email/1.3.0/src/app.py +++ b/email/1.3.0/src/app.py @@ -69,13 +69,7 @@ def send_email_shuffle(self, apikey, recipients, subject, body): data = {"targets": targets, "body": body, "subject": subject, "type": "alert", "email_app": True, "reference_execution": self.current_execution_id} - # url = "https://shuffler.io/functions/sendmail" - - url = "https://7692-2405-201-4019-f142-8124-2465-9b7d-4945.ngrok-free.app/functions/sendmail" - - print("apikey: ", apikey, " authorization: ", self.authorization) - - time.sleep(10) + url = "https://shuffler.io/functions/sendmail" if apikey.strip() == "" and self.authorization != "standalone": apikey = self.authorization From b2933db999f0acdca8bba006310e25261984599f Mon Sep 17 00:00:00 2001 From: Frikky Date: Tue, 12 Aug 2025 12:16:49 +0200 Subject: [PATCH 227/259] Added Search Datastore Category action --- http/1.0.0/api.yaml | 321 ++++++++++++++++++++-- http/1.0.0/requirements.txt | 3 +- http/1.0.0/src/app.py | 386 +++++++++++++++++++++++---- shuffle-ai/1.0.0/Dockerfile | 2 +- shuffle-tools/1.0.0/requirements.txt | 12 +- shuffle-tools/1.2.0/api.yaml | 95 ++++--- shuffle-tools/1.2.0/src/app.py | 103 ++++++- 7 files changed, 802 insertions(+), 120 deletions(-) diff --git a/http/1.0.0/api.yaml b/http/1.0.0/api.yaml index b73b44ed..924b40df 100644 --- a/http/1.0.0/api.yaml +++ b/http/1.0.0/api.yaml @@ -6,26 +6,13 @@ tags: - Testing - HTTP categories: - - Testing + - Other - HTTP contact_info: name: "@frikkylikeme" url: https://github.com/frikky email: "frikky@shuffler.io" actions: - - name: curl - description: Run a curl command - parameters: - - name: statement - description: The curl command to run - multiline: true - example: "curl https://example.com" - required: true - schema: - type: string - returns: - schema: - type: string - name: GET description: Runs a GET request towards the specified endpoint parameters: @@ -40,7 +27,21 @@ actions: description: Headers to use multiline: true required: false - example: "Authorization: Bearer asd\nContent-Type: application/json" + example: "Content-Type: application/json" + schema: + type: string + - name: username + description: The username to use + multiline: false + required: false + example: "Username" + schema: + type: string + - name: password + description: The password to use + multiline: false + required: false + example: "*****" schema: type: string - name: verify @@ -53,10 +54,54 @@ actions: example: "false" schema: type: bool + - name: http_proxy + description: Add a HTTP proxy + multiline: false + required: false + example: "http://192.168.0.1:8080" + schema: + type: bool + - name: https_proxy + description: Add a HTTPS proxy + multiline: false + required: false + example: "http://192.168.0.1:8080" + schema: + type: bool + - name: timeout + description: Add a timeout for the request, in seconds + multiline: false + required: false + example: "10" + schema: + type: bool + - name: to_file + description: Makes the response into a file, and returns it as an ID + multiline: false + required: false + options: + - false + - true + example: "true" + schema: + type: bool returns: schema: type: string - example: "404 NOT FOUND" + example: | + { + "status": 200, + "body": { + "example": "json" + "data": "json" + }, + "url": "https://example.com", + "headers": { + "Content-Type": "application/json" + }, + "cookies": {}, + "success": true + } - name: POST description: Runs a POST request towards the specified endpoint parameters: @@ -78,7 +123,21 @@ actions: description: Headers to use multiline: true required: false - example: "Authorization: Bearer asd\nContent-Type: application/json" + example: "Content-Type: application/json" + schema: + type: string + - name: username + description: The username to use + multiline: false + required: false + example: "Username" + schema: + type: string + - name: password + description: The password to use + multiline: false + required: false + example: "*****" schema: type: string - name: verify @@ -91,12 +150,33 @@ actions: example: "false" schema: type: bool + - name: http_proxy + description: Add a HTTP proxy + multiline: false + required: false + example: "http://192.168.0.1:8080" + schema: + type: bool + - name: https_proxy + description: Add a HTTPS proxy + multiline: false + required: false + example: "http://192.168.0.1:8080" + schema: + type: bool + - name: timeout + description: Add a timeout for the request, in seconds + multiline: false + required: false + example: "10" + schema: + type: bool returns: schema: type: string example: "404 NOT FOUND" - name: PATCH - description: Runs a PATCHrequest towards the specified endpoint + description: Runs a PATCH request towards the specified endpoint parameters: - name: url description: The URL to post to @@ -116,7 +196,21 @@ actions: description: Headers to use multiline: true required: false - example: "Authorization: Bearer asd\nContent-Type: application/json" + example: "Content-Type: application/json" + schema: + type: string + - name: username + description: The username to use + multiline: false + required: false + example: "Username" + schema: + type: string + - name: password + description: The password to use + multiline: false + required: false + example: "*****" schema: type: string - name: verify @@ -129,6 +223,27 @@ actions: example: "false" schema: type: bool + - name: http_proxy + description: Add a HTTP proxy + multiline: false + required: false + example: "http://192.168.0.1:8080" + schema: + type: bool + - name: https_proxy + description: Add a HTTPS proxy + multiline: false + required: false + example: "http://192.168.0.1:8080" + schema: + type: bool + - name: timeout + description: Add a timeout for the request, in seconds + multiline: false + required: false + example: "10" + schema: + type: bool returns: schema: type: string @@ -154,7 +269,21 @@ actions: description: Headers to use multiline: true required: false - example: "Authorization: Bearer asd\nContent-Type: application/json" + example: "Content-Type: application/json" + schema: + type: string + - name: username + description: The username to use + multiline: false + required: false + example: "Username" + schema: + type: string + - name: password + description: The password to use + multiline: false + required: false + example: "*****" schema: type: string - name: verify @@ -167,6 +296,27 @@ actions: example: "false" schema: type: bool + - name: http_proxy + description: Add a HTTP proxy + multiline: false + required: false + example: "http://192.168.0.1:8080" + schema: + type: bool + - name: https_proxy + description: Add a HTTPS proxy + multiline: false + required: false + example: "http://192.168.0.1:8080" + schema: + type: bool + - name: timeout + description: Add a timeout for the request, in seconds + multiline: false + required: false + example: "10" + schema: + type: bool returns: schema: type: string @@ -181,11 +331,32 @@ actions: required: true schema: type: string + - name: body + description: The body to use + multiline: true + example: "{\n\t'json': 'blob'\n}" + required: false + schema: + type: string - name: headers description: Headers to use multiline: true required: false - example: "Authorization: Bearer asd\nContent-Type: application/json" + example: "Content-Type: application/json" + schema: + type: string + - name: username + description: The username to use + multiline: false + required: false + example: "Username" + schema: + type: string + - name: password + description: The password to use + multiline: false + required: false + example: "*****" schema: type: string - name: verify @@ -198,6 +369,27 @@ actions: example: "false" schema: type: bool + - name: http_proxy + description: Add a HTTP proxy + multiline: false + required: false + example: "http://192.168.0.1:8080" + schema: + type: bool + - name: https_proxy + description: Add a HTTPS proxy + multiline: false + required: false + example: "http://192.168.0.1:8080" + schema: + type: bool + - name: timeout + description: Add a timeout for the request, in seconds + multiline: false + required: false + example: "10" + schema: + type: bool returns: schema: type: string @@ -216,7 +408,21 @@ actions: description: Headers to use multiline: true required: false - example: "Authorization: Bearer asd\nContent-Type: application/json" + example: "Content-Type: application/json" + schema: + type: string + - name: username + description: The username to use + multiline: false + required: false + example: "Username" + schema: + type: string + - name: password + description: The password to use + multiline: false + required: false + example: "*****" schema: type: string - name: verify @@ -229,6 +435,27 @@ actions: example: "false" schema: type: bool + - name: http_proxy + description: Add a HTTP proxy + multiline: false + required: false + example: "http://192.168.0.1:8080" + schema: + type: bool + - name: https_proxy + description: Add a HTTPS proxy + multiline: false + required: false + example: "http://192.168.0.1:8080" + schema: + type: bool + - name: timeout + description: Add a timeout for the request, in seconds + multiline: false + required: false + example: "10" + schema: + type: bool returns: schema: type: string @@ -247,7 +474,21 @@ actions: description: Headers to use multiline: true required: false - example: "Authorization: Bearer asd\nContent-Type: application/json" + example: "Content-Type: application/json" + schema: + type: string + - name: username + description: The username to use + multiline: false + required: false + example: "Username" + schema: + type: string + - name: password + description: The password to use + multiline: false + required: false + example: "*****" schema: type: string - name: verify @@ -260,8 +501,42 @@ actions: example: "false" schema: type: bool + - name: http_proxy + description: Add a HTTP proxy + multiline: false + required: false + example: "http://192.168.0.1:8080" + schema: + type: bool + - name: https_proxy + description: Add a HTTPS proxy + multiline: false + required: false + example: "http://192.168.0.1:8080" + schema: + type: bool + - name: timeout + description: Add a timeout for the request, in seconds + multiline: false + required: false + example: "10" + schema: + type: bool returns: schema: type: string example: "404 NOT FOUND" + - name: curl + description: Run a curl command + parameters: + - name: statement + description: The curl command to run + multiline: true + example: "curl https://example.com" + required: true + schema: + type: string + returns: + schema: + type: string large_image: data:image/png;base64,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 diff --git a/http/1.0.0/requirements.txt b/http/1.0.0/requirements.txt index 2774f2f0..d91f4447 100644 --- a/http/1.0.0/requirements.txt +++ b/http/1.0.0/requirements.txt @@ -1,3 +1,2 @@ uncurl==0.0.10 -requests==2.32.4 -shuffle-sdk==0.0.26 +shuffle_sdk diff --git a/http/1.0.0/src/app.py b/http/1.0.0/src/app.py index 8b4f2218..53c64b05 100755 --- a/http/1.0.0/src/app.py +++ b/http/1.0.0/src/app.py @@ -1,6 +1,6 @@ import time import json -import json +import ast import random import socket import uncurl @@ -8,7 +8,7 @@ import requests import subprocess -from shuffle_sdk import AppBase +from shuffle_sdk import AppBase class HTTP(AppBase): __version__ = "1.0.0" @@ -26,6 +26,7 @@ def __init__(self, redis, logger, console_logger=None): # This is dangerously fun :) # Do we care about arbitrary code execution here? + # Probably not huh def curl(self, statement): process = subprocess.Popen(statement, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, shell=True) stdout = process.communicate() @@ -44,18 +45,6 @@ def curl(self, statement): return item return item - #try: - # if not statement.startswith("curl "): - # statement = "curl %s" % statement - - # data = uncurl.parse(statement) - # request = eval(data) - # if isinstance(request, requests.models.Response): - # return request.text - # else: - # return "Unable to parse the curl parameter. Remember to start with curl " - #except: - # return "An error occurred during curl parsing" def splitheaders(self, headers): parsed_headers = {} @@ -63,12 +52,8 @@ def splitheaders(self, headers): split_headers = headers.split("\n") self.logger.info(split_headers) for header in split_headers: - if ": " in header: - splititem = ": " - elif ":" in header: + if ":" in header: splititem = ":" - elif "= " in header: - splititem = "= " elif "=" in header: splititem = "=" else: @@ -76,8 +61,8 @@ def splitheaders(self, headers): continue splitheader = header.split(splititem) - if len(splitheader) == 2: - parsed_headers[splitheader[0]] = splitheader[1] + if len(splitheader) >= 2: + parsed_headers[splitheader[0].strip()] = splititem.join(splitheader[1:]).strip() else: self.logger.info("Skipping header %s with split %s cus only one item" % (header, splititem)) continue @@ -85,81 +70,376 @@ def splitheaders(self, headers): return parsed_headers def checkverify(self, verify): - if verify == None: + if str(verify).lower().strip() == "false": + return False + elif verify == None: return False elif verify: return True elif not verify: return False - elif verify.lower().strip() == "false": - return False else: return True def checkbody(self, body): # Indicates json - if body.strip().startswith("{"): - body = body.replace("\'", "\"") + if isinstance(body, str): + if body.strip().startswith("{"): + body = json.dumps(ast.literal_eval(body)) - # Not sure if loading is necessary - # Seemed to work with plain string into data=body too, and not parsed json=body - #try: - # body = json.loads(body) - #except json.decoder.JSONDecodeError as e: - # return body - return body - else: - return body + # Not sure if loading is necessary + # Seemed to work with plain string into data=body too, and not parsed json=body + #try: + # body = json.loads(body) + #except json.decoder.JSONDecodeError as e: + # return body + + return body + else: + return body + + if isinstance(body, dict) or isinstance(body, list): + try: + body = json.dumps(body) + except: + return body + + return body + + def fix_url(self, url): + # Random bugs seen by users + if "hhttp" in url: + url = url.replace("hhttp", "http") + + if "http:/" in url and not "http://" in url: + url = url.replace("http:/", "http://", -1) + if "https:/" in url and not "https://" in url: + url = url.replace("https:/", "https://", -1) + if "http:///" in url: + url = url.replace("http:///", "http://", -1) + if "https:///" in url: + url = url.replace("https:///", "https://", -1) + if not "http://" in url and not "http" in url: + url = f"http://{url}" + + return url + + def return_file(self, requestdata): + filedata = { + "filename": "response.txt", + "data": requestdata, + } + fileret = self.set_files([filedata]) + if len(fileret) == 1: + return {"success": True, "file_id": fileret[0]} + + return fileret + + def prepare_response(self, request): + try: + parsedheaders = {} + for key, value in request.headers.items(): + parsedheaders[key] = value + + cookies = {} + if request.cookies: + for key, value in request.cookies.items(): + cookies[key] = value + + + jsondata = request.text + try: + jsondata = json.loads(jsondata) + except: + pass + + parseddata = { + "status": request.status_code, + "body": jsondata, + "url": request.url, + "headers": parsedheaders, + "cookies":cookies, + "success": True, + } + + return json.dumps(parseddata) + except Exception as e: + print(f"[WARNING] Failed in request: {e}") + return request.text + + def GET(self, url, headers="", username="", password="", verify=True, http_proxy="", https_proxy="", timeout=5, to_file=False): + url = self.fix_url(url) - def GET(self, url, headers="", verify=True): parsed_headers = self.splitheaders(headers) + parsed_headers["User-Agent"] = "Shuffle Automation" verify = self.checkverify(verify) - return requests.get(url, headers=parsed_headers, verify=verify).text - def POST(self, url, headers="", body="", verify=True): + proxies = {} + if http_proxy: + proxies["http"] = http_proxy + if https_proxy: + proxies["https"] = https_proxy + + auth=None + if username or password: + # Shouldn't be used if authorization headers exist + if "Authorization" in parsed_headers: + #print("Found authorization - skipping username & pw") + pass + else: + auth = requests.auth.HTTPBasicAuth(username, password) + + if not timeout: + timeout = 5 + if timeout: + timeout = int(timeout) + + if to_file == "true": + to_file = True + else: + to_file = False + + request = requests.get(url, headers=parsed_headers, auth=auth, verify=verify, proxies=proxies, timeout=timeout) + if not to_file: + return self.prepare_response(request) + + return self.return_file(request.text) + + def POST(self, url, headers="", body="", username="", password="", verify=True, http_proxy="", https_proxy="", timeout=5, to_file=False): + url = self.fix_url(url) + parsed_headers = self.splitheaders(headers) + parsed_headers["User-Agent"] = "Shuffle Automation" verify = self.checkverify(verify) body = self.checkbody(body) - return requests.post(url, headers=parsed_headers, data=body, verify=verify).text + proxies = {} + if http_proxy: + proxies["http"] = http_proxy + if https_proxy: + proxies["https"] = https_proxy + + auth=None + if username or password: + # Shouldn't be used if authorization headers exist + if "Authorization" in parsed_headers: + #print("Found authorization - skipping username & pw") + pass + else: + auth = requests.auth.HTTPBasicAuth(username, password) + + if not timeout: + timeout = 5 + if timeout: + timeout = int(timeout) + + if to_file == "true": + to_file = True + else: + to_file = False + + request = requests.post(url, headers=parsed_headers, auth=auth, data=body, verify=verify, proxies=proxies, timeout=timeout) + if not to_file: + return self.prepare_response(request) + + return self.return_file(request.text) + + def PUT(self, url, headers="", body="", username="", password="", verify=True, http_proxy="", https_proxy="", timeout=5, to_file=False): + url = self.fix_url(url) - # UNTESTED BELOW HERE - def PUT(self, url, headers="", body="", verify=True): parsed_headers = self.splitheaders(headers) + parsed_headers["User-Agent"] = "Shuffle Automation" verify = self.checkverify(verify) body = self.checkbody(body) - return requests.put(url, headers=parsed_headers, data=body, verify=verify).text + proxies = {} + if http_proxy: + proxies["http"] = http_proxy + if https_proxy: + proxies["https"] = https_proxy + + + auth=None + if username or password: + # Shouldn't be used if authorization headers exist + if "Authorization" in parsed_headers: + #print("Found authorization - skipping username & pw") + pass + else: + auth = requests.auth.HTTPBasicAuth(username, password) + + if not timeout: + timeout = 5 + if timeout: + timeout = int(timeout) + + if to_file == "true": + to_file = True + else: + to_file = False + + request = requests.put(url, headers=parsed_headers, auth=auth, data=body, verify=verify, proxies=proxies, timeout=timeout) + if not to_file: + return self.prepare_response(request) + + return self.return_file(request.text) + + def PATCH(self, url, headers="", body="", username="", password="", verify=True, http_proxy="", https_proxy="", timeout=5, to_file=False): + url = self.fix_url(url) - def PATCH(self, url, headers="", body="", verify=True): parsed_headers = self.splitheaders(headers) + parsed_headers["User-Agent"] = "Shuffle Automation" verify = self.checkverify(verify) body = self.checkbody(body) - return requests.patch(url, headers=parsed_headers, data=body, verify=verify).text + proxies = {} + if http_proxy: + proxies["http"] = http_proxy + if https_proxy: + proxies["https"] = https_proxy + + auth=None + if username or password: + # Shouldn't be used if authorization headers exist + if "Authorization" in parsed_headers: + #print("Found authorization - skipping username & pw") + pass + else: + auth = requests.auth.HTTPBasicAuth(username, password) + + if not timeout: + timeout = 5 + if timeout: + timeout = int(timeout) + + if to_file == "true": + to_file = True + else: + to_file = False + + request = requests.patch(url, headers=parsed_headers, data=body, auth=auth, verify=verify, proxies=proxies, timeout=timeout) + if not to_file: + return self.prepare_response(request) + + return self.return_file(request.text) + + def DELETE(self, url, headers="", body="", username="", password="", verify=True, http_proxy="", https_proxy="", timeout=5, to_file=False): + url = self.fix_url(url) - def DELETE(self, url, headers="", body="", verify=True): parsed_headers = self.splitheaders(headers) + parsed_headers["User-Agent"] = "Shuffle Automation" verify = self.checkverify(verify) - return requests.delete(url, headers=parsed_headers, verify=verify).text + body = self.checkbody(body) + proxies = {} + if http_proxy: + proxies["http"] = http_proxy + if https_proxy: + proxies["https"] = https_proxy + + auth=None + if username or password: + # Shouldn't be used if authorization headers exist + if "Authorization" in parsed_headers: + #print("Found authorization - skipping username & pw") + pass + else: + auth = requests.auth.HTTPBasicAuth(username, password) + + if not timeout: + timeout = 5 + if timeout: + timeout = int(timeout) + + if to_file == "true": + to_file = True + else: + to_file = False + + request = requests.delete(url, headers=parsed_headers, data=body, auth=auth, verify=verify, proxies=proxies, timeout=timeout) + if not to_file: + return self.prepare_response(request) + + return self.return_file(request.text) + + def HEAD(self, url, headers="", body="", username="", password="", verify=True, http_proxy="", https_proxy="", timeout=5, to_file=False): + url = self.fix_url(url) - def HEAD(self, url, headers="", body="", verify=True): parsed_headers = self.splitheaders(headers) + parsed_headers["User-Agent"] = "Shuffle Automation" verify = self.checkverify(verify) body = self.checkbody(body) - return requests.head(url, headers=parsed_headers, verify=verify).text + proxies = {} + if http_proxy: + proxies["http"] = http_proxy + if https_proxy: + proxies["https"] = https_proxy + + auth=None + if username or password: + # Shouldn't be used if authorization headers exist + if "Authorization" in parsed_headers: + #print("Found authorization - skipping username & pw") + pass + else: + auth = requests.auth.HTTPBasicAuth(username, password) + + if not timeout: + timeout = 5 + if timeout: + timeout = int(timeout) + + if to_file == "true": + to_file = True + else: + to_file = False + + request = requests.head(url, headers=parsed_headers, auth=auth, verify=verify, proxies=proxies, timeout=timeout) + if not to_file: + return self.prepare_response(request) + + return self.return_file(request.text) + + def OPTIONS(self, url, headers="", body="", username="", password="", verify=True, http_proxy="", https_proxy="", timeout=5, to_file=False): + url = self.fix_url(url) - def OPTIONS(self, url, headers="", body="", verify=True): parsed_headers = self.splitheaders(headers) + parsed_headers["User-Agent"] = "Shuffle Automation" verify = self.checkverify(verify) body = self.checkbody(body) - return requests.options(url, headers=parsed_headers, verify=verify).text + proxies = {} + if http_proxy: + proxies["http"] = http_proxy + if https_proxy: + proxies["https"] = https_proxy + + auth=None + if username or password: + # Shouldn't be used if authorization headers exist + if "Authorization" in parsed_headers: + #print("Found authorization - skipping username & pw") + pass + else: + auth = requests.auth.HTTPBasicAuth(username, password) + + if not timeout: + timeout = 5 + + if timeout: + timeout = int(timeout) + + if to_file == "true": + to_file = True + else: + to_file = False + + request = requests.options(url, headers=parsed_headers, auth=auth, verify=verify, proxies=proxies, timeout=timeout) + if not to_file: + return self.prepare_response(request) + + return self.return_file(request.text) # Run the actual thing after we've checked params def run(request): - print("Starting cloud!") action = request.get_json() - print(action) - print(type(action)) authorization_key = action.get("authorization") current_execution_id = action.get("execution_id") diff --git a/shuffle-ai/1.0.0/Dockerfile b/shuffle-ai/1.0.0/Dockerfile index 319b55f1..5d4426d2 100644 --- a/shuffle-ai/1.0.0/Dockerfile +++ b/shuffle-ai/1.0.0/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.10-slim +FROM python:3.10.18-slim ### Install Tesseract ENV SHELL=/bin/bash diff --git a/shuffle-tools/1.0.0/requirements.txt b/shuffle-tools/1.0.0/requirements.txt index cd48a310..89ca3320 100644 --- a/shuffle-tools/1.0.0/requirements.txt +++ b/shuffle-tools/1.0.0/requirements.txt @@ -1,8 +1,12 @@ -ioc_finder==6.0.1 -py7zr==0.20.2 -rarfile==4.0 +ioc_finder==7.3.0 +py7zr==0.22.0 +rarfile==4.2 pyminizip==0.2.6 requests==2.32.4 -xmltodict==0.11.0 +xmltodict==0.14.2 json2xml==5.0.5 ipaddress==1.0.23 +google.auth==2.37.0 +paramiko==3.5.0 +shuffle-sdk + diff --git a/shuffle-tools/1.2.0/api.yaml b/shuffle-tools/1.2.0/api.yaml index 5867a19c..301b9f51 100644 --- a/shuffle-tools/1.2.0/api.yaml +++ b/shuffle-tools/1.2.0/api.yaml @@ -36,51 +36,28 @@ actions: schema: type: string - - name: dedup_and_merge - description: Merges data from multiple workflows within a set timeframe. Returns action as SKIPPED if the data is a duplicate. Returns with a list of all data if the data at the end + - name: search_datastore_category + description: Checks whether keys within a list are already in datastore, and returns whether they existed or not. They will be automatically appended. parameters: - - name: key - description: The key to use for deduplication - required: true - multiline: false - example: "ticketname+username" - schema: - type: string - - name: value - description: The full value of the item + - name: input_list + description: The list to check from. Don't use .# in this. required: true multiline: true - example: "1208301599081" + example: '[{"data": "1.2.3.4"}, {"data": "1.2.3.5"}]' schema: type: string - - name: timeout - description: The timeout before returning + - name: key + description: The key to use for deduplication. MUST be a part of each key in the input_list. required: true - options: - - 2 - - 3 - - 4 - - 5 - - 6 - - 7 - - 8 - - 9 - - 10 - - 15 - - 20 - - 25 multiline: false - example: "1" + example: "ticketname+username" schema: type: string - - name: set_skipped - description: Whether to set the action SKIPPED or not IF it matches another workflow in the same timeframe + - name: category + description: The category you want to upload to. This can be a new category, or an existing one. required: true - options: - - true - - false - multiline: false - example: "true" + multiline: false + example: "tickets" schema: type: string @@ -253,6 +230,54 @@ actions: # returns: # schema: # type: string + - name: dedup_and_merge + description: Merges data from multiple apps within a set timeframe. Returns action as SKIPPED if the data is a duplicate. Returns with a list of all data if the data at the end + parameters: + - name: key + description: The key to use for deduplication + required: true + multiline: false + example: "ticketname+username" + schema: + type: string + - name: value + description: The full value of the item + required: true + multiline: true + example: "1208301599081" + schema: + type: string + - name: timeout + description: The timeout before returning + required: true + options: + - 2 + - 3 + - 4 + - 5 + - 6 + - 7 + - 8 + - 9 + - 10 + - 15 + - 20 + - 25 + multiline: false + example: "1" + schema: + type: string + - name: set_skipped + description: Whether to set the action SKIPPED or not IF it matches another workflow in the same timeframe + required: true + options: + - true + - false + multiline: false + example: "true" + schema: + type: string + - name: filter_list description: Takes a list and filters based on your data skip_multicheck: true diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 12389cc6..3dce6b0f 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -2124,9 +2124,15 @@ def change_cache_subkey(self, key, subkey, value, overwrite, category=""): self.logger.info("Value couldn't be parsed") return response.text + def delete_datastore_value(self, key, category=""): + return self.delete_cache(key, category=category) + def delete_cache_value(self, key, category=""): return self.delete_cache(key, category=category) + def get_datastore_value(self, key, category=""): + return self.get_cache_value(key, category=category) + def get_cache_value(self, key, category=""): org_id = self.full_execution["workflow"]["execution_org"]["id"] url = "%s/api/v1/orgs/%s/get_cache" % (self.url, org_id) @@ -2135,7 +2141,7 @@ def get_cache_value(self, key, category=""): "execution_id": self.current_execution_id, "authorization": self.authorization, "org_id": org_id, - "key": key, + "key": str(key), } if category: @@ -2164,6 +2170,99 @@ def get_cache_value(self, key, category=""): self.logger.info("Value couldn't be parsed, or json dump of value failed") return value.text + def set_datastore_value(self, key, value, category=""): + return set_cache_value(self, key, value, category=category) + + # Check if a specific key exists in a datastore category or not + # Otherwise appends it automatically + def search_datastore_category(self, input_list, key, category): + returnvalue = { + "success": False, + "category": category, + "new": [], + "exists": [], + } + + if len(key) == 0 or len(category) == 0: + returnvalue["reason"] = "Key and/or Category is empty" + return returnvalue + + data = [] + if isinstance(input_list, dict): + input_list = [input_list] + + if not isinstance(input_list, list): + try: + input_list = json.loads(str(input_list)) + except Exception as e: + returnvalue["reason"] = f"Input list is not a valid JSON list: {input_list}", + returnvalue["details"] = str(e) + return returnvalue + + org_id = self.full_execution["workflow"]["execution_org"]["id"] + cnt = -1 + for item in input_list: + cnt += 1 + if not isinstance(item, dict): + try: + item = json.loads(str(item)) + except Exception as e: + self.logger.info("[ERROR][%s] Failed to parse item as JSON: %s" % (self.current_execution_id, e)) + continue + + input_list[cnt] = item + if key not in item: + returnvalue["reason"] = "Couldn't find key '%s' in every item. Make sure to use a key that exists in every entry." % (key), + return returnvalue + + data.append({ + "workflow_id": self.full_execution["workflow"]["id"], + "execution_id": self.current_execution_id, + "authorization": self.authorization, + "org_id": org_id, + "key": str(item[key]), + "value": json.dumps(item), + "category": category, + }) + + url = f"{self.url}/api/v2/datastore?bulk=true&execution_id={self.current_execution_id}&authorization={self.authorization}" + response = requests.post(url, json=data, verify=False) + if response.status_code != 200: + returnvalue["reason"] = "Failed to check datastore key exists" + returnvalue["details"] = response.text + returnvalue["status"] = response.status_code + return returnvalue + + data = "" + try: + data = response.json() + except json.decoder.JSONDecodeError as e: + return response.text + + if "keys_existed" not in data: + return data + + returnvalue["success"] = True + for datastore_item in input_list: + found = False + for existing_item in data["keys_existed"]: + if existing_item["key"] != datastore_item[key]: + continue + + if existing_item["existed"]: + returnvalue["exists"].append(datastore_item) + else: + returnvalue["new"].append(datastore_item) + + found = True + break + + if not found: + print("[ERROR][%s] Key %s not found in datastore response, adding as new" % (self.current_execution_id, datastore_item[key])) + returnvalue["new"].append(datastore_item) + + return json.dumps(returnvalue, indent=4) + def set_cache_value(self, key, value, category=""): org_id = self.full_execution["workflow"]["execution_org"]["id"] url = "%s/api/v1/orgs/%s/set_cache" % (self.url, org_id) @@ -2182,7 +2281,7 @@ def set_cache_value(self, key, value, category=""): "execution_id": self.current_execution_id, "authorization": self.authorization, "org_id": org_id, - "key": key, + "key": str(key), "value": value, } From a93a13c4e125f2f79d9d17fdaf24cd5810188f87 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 16:28:17 +0000 Subject: [PATCH 228/259] Bump requests in /email/1.3.0 in the pip group across 1 directory Bumps the pip group with 1 update in the /email/1.3.0 directory: [requests](https://github.com/psf/requests). Updates `requests` from 2.32.3 to 2.32.4 - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.3...v2.32.4) --- updated-dependencies: - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production dependency-group: pip ... Signed-off-by: dependabot[bot] --- email/1.3.0/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/email/1.3.0/requirements.txt b/email/1.3.0/requirements.txt index 90d0c96d..5d6b0d2c 100644 --- a/email/1.3.0/requirements.txt +++ b/email/1.3.0/requirements.txt @@ -1,4 +1,4 @@ -requests==2.32.3 +requests==2.32.4 glom==20.11.0 jsonpickle==2.0.0 From ca8d8dc6b19d634694ec8edb8aa691ca190034e1 Mon Sep 17 00:00:00 2001 From: Yash Singh Date: Wed, 20 Aug 2025 19:52:28 +0530 Subject: [PATCH 229/259] Update README.md --- postgress/1.0.0/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/postgress/1.0.0/README.md b/postgress/1.0.0/README.md index 12b051c2..96f475a4 100644 --- a/postgress/1.0.0/README.md +++ b/postgress/1.0.0/README.md @@ -1,2 +1,3 @@ # PostgreSQL Shuffle App This app connects to PostgreSQL and executes queries. + From 042a53ce06d696035c89d0f2371674a70b7da516 Mon Sep 17 00:00:00 2001 From: Yash Singh Date: Wed, 20 Aug 2025 19:57:37 +0530 Subject: [PATCH 230/259] Update requirements.txt --- email/1.3.0/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/email/1.3.0/requirements.txt b/email/1.3.0/requirements.txt index 5d6b0d2c..90d0c96d 100644 --- a/email/1.3.0/requirements.txt +++ b/email/1.3.0/requirements.txt @@ -1,4 +1,4 @@ -requests==2.32.4 +requests==2.32.3 glom==20.11.0 jsonpickle==2.0.0 From 5e287ef49cfb87d4d3532c872c165be424b10570 Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 25 Aug 2025 23:49:50 +0200 Subject: [PATCH 231/259] Minor fixes to shuffle-ai app to ensure it uploads Singul fields properly --- shuffle-ai/1.0.0/requirements.txt | 4 ++-- shuffle-ai/1.0.0/src/app.py | 14 +++++++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/shuffle-ai/1.0.0/requirements.txt b/shuffle-ai/1.0.0/requirements.txt index a0a284d0..403fc0b0 100644 --- a/shuffle-ai/1.0.0/requirements.txt +++ b/shuffle-ai/1.0.0/requirements.txt @@ -1,7 +1,7 @@ -shuffle_sdk==0.0.11 +shuffle-sdk==0.0.28 + pytesseract pdf2image pypdf2 -requests llama-cpp-python diff --git a/shuffle-ai/1.0.0/src/app.py b/shuffle-ai/1.0.0/src/app.py index eb025b40..14de1517 100644 --- a/shuffle-ai/1.0.0/src/app.py +++ b/shuffle-ai/1.0.0/src/app.py @@ -89,7 +89,7 @@ def load_llm_model(model): "reason": "Failed to load LLM model %s" % model, } -class Tools(AppBase): +class ShuffleAI(AppBase): __version__ = "1.0.0" app_name = "Shuffle AI" @@ -430,6 +430,7 @@ def gpt(self, input_text): def run_schemaless(self, category, action, app_name="", fields=""): self.logger.info("[DEBUG] Running schemaless action with category '%s' and action label '%s'" % (category, action)) + # Not necessary anymore """ action := shuffle.CategoryAction{ Label: step.Name, @@ -462,9 +463,16 @@ def run_schemaless(self, category, action, app_name="", fields=""): elif isinstance(fields, dict): for key, value in fields.items(): + parsedvalue = str(value) + try: + if str(value).startswith("{") or str(value).startswith("["): + parsedvalue = json.dumps(value) + except: + pass + data["fields"].append({ "key": key, - "value": str(value), + "value": parsedvalue, }) else: @@ -530,4 +538,4 @@ def run_schemaless(self, category, action, app_name="", fields=""): return request.text if __name__ == "__main__": - Tools.run() + ShuffleAI.run() From 7960e85914c175b7d051aa2ad38f30dad91e4bbb Mon Sep 17 00:00:00 2001 From: Frikky Date: Tue, 26 Aug 2025 20:37:58 +0200 Subject: [PATCH 232/259] Minor change to how schemaless is called --- email/1.3.0/requirements.txt | 2 +- shuffle-ai/1.0.0/src/app.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/email/1.3.0/requirements.txt b/email/1.3.0/requirements.txt index 90d0c96d..bcb8f684 100644 --- a/email/1.3.0/requirements.txt +++ b/email/1.3.0/requirements.txt @@ -4,4 +4,4 @@ jsonpickle==2.0.0 eml-parser==2.0.0 msg-parser==1.2.0 -shuffle_sdk==0.0.11 +shuffle_sdk==0.0.28 diff --git a/shuffle-ai/1.0.0/src/app.py b/shuffle-ai/1.0.0/src/app.py index 14de1517..8313ab6e 100644 --- a/shuffle-ai/1.0.0/src/app.py +++ b/shuffle-ai/1.0.0/src/app.py @@ -499,7 +499,8 @@ def run_schemaless(self, category, action, app_name="", fields=""): }) - baseurl = "%s/api/v1/apps/categories/run" % self.base_url + #baseurl = "%s/api/v1/apps/categories/run" % self.base_url + baseurl = "%s/api/v1/apps/categories/run" % self.url baseurl += "?execution_id=%s&authorization=%s" % (self.current_execution_id, self.authorization) self.logger.info("[DEBUG] Running schemaless action with URL '%s', category %s and action label %s" % (baseurl, category, action)) From 678ae0f5b6ea6a57891422c2d70049db6a78d82d Mon Sep 17 00:00:00 2001 From: Frikky Date: Tue, 26 Aug 2025 23:33:08 +0200 Subject: [PATCH 233/259] Made shuffle AI rebuild work --- shuffle-ai/1.0.0/Dockerfile | 100 ++++++++---------------------- shuffle-ai/1.0.0/requirements.txt | 2 - 2 files changed, 25 insertions(+), 77 deletions(-) diff --git a/shuffle-ai/1.0.0/Dockerfile b/shuffle-ai/1.0.0/Dockerfile index 5d4426d2..9b059f27 100644 --- a/shuffle-ai/1.0.0/Dockerfile +++ b/shuffle-ai/1.0.0/Dockerfile @@ -1,104 +1,54 @@ -FROM python:3.10.18-slim - -### Install Tesseract -ENV SHELL=/bin/bash -ENV CC /usr/bin/clang -ENV CXX /usr/bin/clang++ -ENV LANG=C.UTF-8 -ENV TESSDATA_PREFIX=/usr/local/share/tessdata - -# Install all build tools needed for our pip installs -RUN apt update -RUN apt install -y clang g++ make automake autoconf libtool cmake - -## Install the same packages with apt as with apk, but ensure they exist in apt -RUN apt install -y jq git curl -RUN apt install -y file openssl bash tini libpng-dev aspell-en -RUN apt install -y git clang g++ make automake autoconf libtool cmake -RUN apt install -y autoconf-archive wget - -# Install cuda toolkit -#RUN wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/cuda-archive-keyring.gpg -#RUN dpkg -i cuda-archive-keyring.gpg -#RUN rm cuda-archive-keyring.gpg -#RUN apt update -#RUN apt install -y cuda -#RUN echo 'export PATH=/usr/local/cuda/bin:$PATH' >> ~/.bashrc -#RUN echo 'export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH' >> ~/.bashrc -#RUN source ~/.bashrc - -# Larger model -RUN mkdir -p /models - -# Fails. 6 bit, 8B model. -#RUN wget https://huggingface.co/RichardErkhov/meta-llama_-_Meta-Llama-3-8B-gguf/blob/main/Meta-Llama-3-8B.Q6_K.gguf?download=true -O /models/Meta-Llama-3-8B.Q6_K.gguf -#ENV MODEL_PATH="/models/Meta-Llama-3-8B.Q6_K.gguf" - -# Simple small Llama wrapper -RUN wget https://huggingface.co/unsloth/DeepSeek-R1-Distill-Llama-8B-GGUF/resolve/main/DeepSeek-R1-Distill-Llama-8B-Q2_K.gguf?download=true -O /models/DeepSeek-R1-Distill-Llama.gguf -# Larger one -#RUN wget https://huggingface.co/unsloth/DeepSeek-R1-Distill-Llama-8B-GGUF/resolve/main/DeepSeek-R1-Distill-Llama-8B-Q8_0.gguf?download=true -O /models/DeepSeek-R1-Distill-Llama.gguf -ENV MODEL_PATH="/models/DeepSeek-R1-Distill-Llama.gguf" - -# Failing? Bad magic bytes. -#RUN wget https://huggingface.co/QuantFactory/Llama-3.2-3B-GGUF/resolve/main/Llama-3.2-3B.Q2_K.gguf?download=true -O /models/Llama-3.2-3B.Q2_K.gguf +FROM python:3.10-alpine +# Install all alpine build tools needed for our pip installs +#RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev git poppler-utils # Install all of our pip packages in a single directory that we can copy to our base image later RUN mkdir /install WORKDIR /install # Switch back to our base image and copy in all of our built packages and source code +#COPY --from=builder /install /usr/local +COPY src /app COPY requirements.txt /requirements.txt RUN python3 -m pip install -r /requirements.txt -RUN CMAKE_ARGS="-DLLAMA_CUBLAS=on" python3 -m pip install llama-cpp-python --upgrade --force-reinstall --no-cache-dir - # Install any binary dependencies needed in our final image +# RUN apk --no-cache add --update my_binary_dependency +RUN apk --no-cache add jq git curl +ENV SHELL=/bin/bash + +### Install Tesseract +ENV CC /usr/bin/clang +ENV CXX /usr/bin/clang++ +ENV LANG=C.UTF-8 +ENV TESSDATA_PREFIX=/usr/local/share/tessdata # Dev tools WORKDIR /tmp -#RUN apk update -#RUN apk upgrade - - +RUN apk update +RUN apk upgrade +RUN apk add file openssl openssl-dev bash tini leptonica-dev openjpeg-dev tiff-dev libpng-dev zlib-dev libgcc mupdf-dev jbig2dec-dev +RUN apk add freetype-dev openblas-dev ffmpeg-dev linux-headers aspell-dev aspell-en # enchant-dev jasper-dev +RUN apk add --virtual .dev-deps git clang clang-dev g++ make automake autoconf libtool pkgconfig cmake ninja +RUN apk add --virtual .dev-testing-deps -X http://dl-3.alpinelinux.org/alpine/edge/testing autoconf-archive RUN ln -s /usr/include/locale.h /usr/include/xlocale.h -#RUN apk add tesseract-ocr -RUN apt install -y tesseract-ocr -#RUN apk add poppler-utils -RUN apt install -y poppler-utils -RUN apt clean && rm -rf /var/lib/apt/lists/* +RUN apk add tesseract-ocr +RUN apk add poppler-utils # Install from main RUN mkdir /usr/local/share/tessdata -RUN wget https://github.com/tesseract-ocr/tessdata_fast/raw/main/eng.traineddata -P /usr/local/share/tessdata - RUN mkdir src RUN cd src - +RUN wget https://github.com/tesseract-ocr/tessdata_fast/raw/main/eng.traineddata -P /usr/local/share/tessdata RUN git clone --depth 1 https://github.com/tesseract-ocr/tesseract.git #RUN curl -fsSL https://ollama.com/install.sh | sh -# Install to /usr/local -#RUN wget https://ollama.com/install.sh -O /usr/local/bin/ollama-install -#RUN chmod +x /usr/local/bin/ollama-install -#RUN sh /usr/local/bin/ollama-install -# -#RUN ls -alh /usr/bin -#RUN ollama serve & sleep 2 && ollama pull nezahatkorkmaz/deepseek-v3 -#CMD ["sh", "-c", "ollama serve & sleep 2 && python app.py --log-level DEBUG"] - -#RUN wget https://huggingface.co/TheBloke/Mistral-7B-Instruct-v0.1-GGUF/resolve/main/mistral-7b-instruct-v0.1.Q4_K_M.gguf -RUN python3 -m pip install ctransformers --no-binary ctransformers +#RUN ollama pull llama3.2 +#RUN cd tesseract && ./autogen.sh && ./configure --build=x86_64-alpine-linux-musl --host=x86_64-alpine-linux-musl && make && make install && cd /tmp/src # Finally, lets run our app! -ENV GIN_MODE=release -ENV SHUFFLE_APP_SDK_TIMEOUT=300 -#ENV LD_LIBRARY_PATH=/usr/local/lib/python3.10/site-packages/ctransformers/lib/basic/libctransformers.so -#RUN chmod 755 /usr/local/lib/python3.10/site-packages/ctransformers/lib/basic/libctransformers.so - -COPY src /app WORKDIR /app CMD ["python", "app.py", "--log-level", "DEBUG"] diff --git a/shuffle-ai/1.0.0/requirements.txt b/shuffle-ai/1.0.0/requirements.txt index 403fc0b0..6cf2c0d7 100644 --- a/shuffle-ai/1.0.0/requirements.txt +++ b/shuffle-ai/1.0.0/requirements.txt @@ -3,5 +3,3 @@ shuffle-sdk==0.0.28 pytesseract pdf2image pypdf2 - -llama-cpp-python From 3f0c23458ea5b88672e0dcf3b39a786ee33ef01f Mon Sep 17 00:00:00 2001 From: Frikky Date: Tue, 26 Aug 2025 23:33:41 +0200 Subject: [PATCH 234/259] Removed local inference from AI app for now --- shuffle-ai/1.0.0/api.yaml | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/shuffle-ai/1.0.0/api.yaml b/shuffle-ai/1.0.0/api.yaml index 8259891d..b9634f39 100644 --- a/shuffle-ai/1.0.0/api.yaml +++ b/shuffle-ai/1.0.0/api.yaml @@ -15,23 +15,23 @@ contact_info: url: https://shuffler.io email: support@shuffler.io actions: - - name: run_llm - description: "Runs a local LLM, with a GPU or CPU (slow). Default model is set up in Dockerfile" - parameters: - - name: input - description: "The input question to the model" - required: true - multiline: true - example: "" - schema: - type: string - - name: system_message - description: "The system message use, if any" - required: false - multiline: false - example: "" - schema: - type: string + #- name: run_llm + # description: "Runs a local LLM, with a GPU or CPU (slow). Default model is set up in Dockerfile" + # parameters: + # - name: input + # description: "The input question to the model" + # required: true + # multiline: true + # example: "" + # schema: + # type: string + # - name: system_message + # description: "The system message use, if any" + # required: false + # multiline: false + # example: "" + # schema: + # type: string - name: shuffle_cloud_inference description: Input ANY kind of data in the format you want, and the format you want it in. Default is a business-y email. Uses ShuffleGPT, which is based on OpenAI and our own model. From e3d61f6073ab627d623aef80b489924d97027083 Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 27 Aug 2025 12:47:19 +0200 Subject: [PATCH 235/259] Removed unused old versions. This is fine as we can always recover them if we HAVE to --- email/1.1.0/Dockerfile | 27 - email/1.1.0/api.yaml | 253 ---- email/1.1.0/requirements.txt | 8 - email/1.1.0/src/app.py | 360 ----- email/1.2.0/Dockerfile | 27 - email/1.2.0/api.yaml | 280 ---- email/1.2.0/requirements.txt | 8 - email/1.2.0/src/analyze.py | 158 --- email/1.2.0/src/app.py | 566 -------- http/1.0.0/Dockerfile | 27 - http/1.0.0/api.yaml | 542 ------- http/1.0.0/requirements.txt | 2 - http/1.0.0/src/app.py | 453 ------ http/1.1.0/Dockerfile | 27 - http/1.1.0/api.yaml | 365 ----- http/1.1.0/requirements.txt | 2 - http/1.1.0/src/app.py | 247 ---- http/1.2.0/Dockerfile | 27 - http/1.2.0/api.yaml | 522 ------- http/1.2.0/requirements.txt | 2 - http/1.2.0/src/app.py | 403 ------ http/1.3.0/Dockerfile | 27 - http/1.3.0/api.yaml | 522 ------- http/1.3.0/requirements.txt | 2 - http/1.3.0/src/app.py | 456 ------ shuffle-tools/1.0.0/Dockerfile | 27 - shuffle-tools/1.0.0/api.yaml | 814 ----------- shuffle-tools/1.0.0/docker-compose.yml | 20 - shuffle-tools/1.0.0/requirements.txt | 12 - shuffle-tools/1.0.0/src/app.py | 1305 ----------------- shuffle-tools/1.1.0/Dockerfile | 27 - shuffle-tools/1.1.0/api.yaml | 950 ------------- shuffle-tools/1.1.0/docker-compose.yml | 15 - shuffle-tools/1.1.0/requirements.txt | 8 - shuffle-tools/1.1.0/src/app.py | 1810 ------------------------ 35 files changed, 10301 deletions(-) delete mode 100644 email/1.1.0/Dockerfile delete mode 100644 email/1.1.0/api.yaml delete mode 100644 email/1.1.0/requirements.txt delete mode 100644 email/1.1.0/src/app.py delete mode 100644 email/1.2.0/Dockerfile delete mode 100644 email/1.2.0/api.yaml delete mode 100644 email/1.2.0/requirements.txt delete mode 100644 email/1.2.0/src/analyze.py delete mode 100644 email/1.2.0/src/app.py delete mode 100644 http/1.0.0/Dockerfile delete mode 100644 http/1.0.0/api.yaml delete mode 100644 http/1.0.0/requirements.txt delete mode 100755 http/1.0.0/src/app.py delete mode 100644 http/1.1.0/Dockerfile delete mode 100644 http/1.1.0/api.yaml delete mode 100644 http/1.1.0/requirements.txt delete mode 100755 http/1.1.0/src/app.py delete mode 100644 http/1.2.0/Dockerfile delete mode 100644 http/1.2.0/api.yaml delete mode 100644 http/1.2.0/requirements.txt delete mode 100755 http/1.2.0/src/app.py delete mode 100644 http/1.3.0/Dockerfile delete mode 100644 http/1.3.0/api.yaml delete mode 100644 http/1.3.0/requirements.txt delete mode 100755 http/1.3.0/src/app.py delete mode 100644 shuffle-tools/1.0.0/Dockerfile delete mode 100644 shuffle-tools/1.0.0/api.yaml delete mode 100644 shuffle-tools/1.0.0/docker-compose.yml delete mode 100644 shuffle-tools/1.0.0/requirements.txt delete mode 100644 shuffle-tools/1.0.0/src/app.py delete mode 100644 shuffle-tools/1.1.0/Dockerfile delete mode 100644 shuffle-tools/1.1.0/api.yaml delete mode 100644 shuffle-tools/1.1.0/docker-compose.yml delete mode 100644 shuffle-tools/1.1.0/requirements.txt delete mode 100644 shuffle-tools/1.1.0/src/app.py diff --git a/email/1.1.0/Dockerfile b/email/1.1.0/Dockerfile deleted file mode 100644 index bcc1273d..00000000 --- a/email/1.1.0/Dockerfile +++ /dev/null @@ -1,27 +0,0 @@ -# Base our app image off of the WALKOFF App SDK image -FROM frikky/shuffle:app_sdk as base - -# We're going to stage away all of the bloat from the build tools so lets create a builder stage -FROM base as builder - -# Install all alpine build tools needed for our pip installs -RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev - -# Install all of our pip packages in a single directory that we can copy to our base image later -RUN mkdir /install -WORKDIR /install -COPY requirements.txt /requirements.txt -RUN pip install --prefix="/install" -r /requirements.txt - -# Switch back to our base image and copy in all of our built packages and source code -FROM base -COPY --from=builder /install /usr/local -COPY src /app - -# Install any binary dependencies needed in our final image -# RUN apk --no-cache add --update my_binary_dependency - -# Finally, lets run our app! -WORKDIR /app -CMD python app.py --log-level DEBUG - diff --git a/email/1.1.0/api.yaml b/email/1.1.0/api.yaml deleted file mode 100644 index 7259f92f..00000000 --- a/email/1.1.0/api.yaml +++ /dev/null @@ -1,253 +0,0 @@ -walkoff_version: 1.1.0 -app_version: 1.1.0 -name: email -description: Email app -tags: - - email -categories: - - communication -contact_info: - name: "@frikkylikeme" - url: https://github.com/frikky - email: "frikky@shuffler.io" -actions: - - name: send_email_shuffle - description: Send an email from Shuffle - parameters: - - name: apikey - description: Your https://shuffler.io apikey - multiline: false - example: "https://shuffler.io apikey" - required: true - schema: - type: string - - name: recipients - description: The recipients of the email - multiline: false - example: "test@example.com,frikky@shuffler.io" - required: true - schema: - type: string - - name: subject - description: The subject to use - multiline: false - example: "SOS this is an alert :o" - required: true - schema: - type: string - - name: body - description: The body to add to the email - multiline: true - example: "This is an email alert from Shuffler.io :)" - required: true - schema: - type: string - returns: - schema: - type: string - - name: send_email_smtp - description: Send an email with SMTP - parameters: - - name: username - description: The SMTP login username - multiline: false - example: "frikky@shuffler.io" - required: true - schema: - type: string - - name: password - description: The password to log in with SMTP - multiline: false - example: "******************" - required: true - schema: - type: string - - name: smtp_host - description: The host of the SMTP - multiline: false - example: "smtp-mail.outlook.com" - required: true - schema: - type: string - - name: smtp_port - description: The port to use for SMTP - multiline: false - example: "587" - required: true - schema: - type: string - - name: recipient - description: The receiver(s) of the email - multiline: false - example: "frikky@shuffler.io,frikky@shuffler.io" - required: true - schema: - type: string - - name: subject - description: The subject of the email - multiline: false - example: "This is a subject, hello there :)" - required: true - schema: - type: string - - name: body - description: The body to add to the email - multiline: true - example: "This is an email alert from Shuffler.io :)" - required: true - schema: - type: string - - name: attachments - description: Send files from shuffle as part of the email - multiline: false - example: "file_id1,file_id2,file_id3" - required: false - schema: - type: string - - name: ssl_verify - description: Whether to use TLS or not - example: "true" - required: false - options: - - true - - false - schema: - type: string - returns: - schema: - type: string - - name: get_emails_imap - description: Get emails using IMAP (e.g. imap.gmail.com / Outlook.office365.com) - parameters: - - name: username - description: The SMTP login username - multiline: false - example: "frikky@shuffler.io" - required: true - schema: - type: string - - name: password - description: The password to log in with SMTP - multiline: false - example: "******************" - required: true - schema: - type: string - - name: imap_server - description: The imap server host - multiline: false - example: "Outlook.office365.com" - required: true - schema: - type: string - - name: foldername - description: The folder to use, e.g. "inbox" - multiline: false - example: "inbox" - required: true - schema: - type: string - - name: amount - description: Amount of emails to retrieve - multiline: false - example: "10" - required: true - schema: - type: string - - name: unread - description: Retrieve just unread emails - multiline: false - options: - - "false" - - "true" - required: true - schema: - type: bool - - name: fields - description: Comma separated list of fields to be exported - multiline: false - example: "body, header.subject, header.header.message-id" - required: false - schema: - type: string - - name: include_raw_body - description: Include raw body in email export - multiline: false - options: - - "false" - - "true" - required: true - schema: - type: bool - - name: include_attachment_data - description: Include raw attachments in email export - multiline: false - options: - - "false" - - "true" - required: true - schema: - type: bool - - name: upload_email_shuffle - description: Upload email in shuffle, return uid - multiline: false - options: - - "false" - - "true" - required: true - schema: - type: bool - - name: upload_attachments_shuffle - description: Upload attachments in shuffle, return uids - multiline: false - options: - - "false" - - "true" - required: true - schema: - type: bool - - name: ssl_verify - description: Whether to use TLS or not - example: "true" - required: false - options: - - true - - false - schema: - type: string - - name: parse_email_file - description: Takes a file from shuffle and analyzes it if it's a valid .eml or .msg - parameters: - - name: file_id - description: file id - required: true - multiline: true - example: 'adf5e3d0fd85633be17004735a0a119e' - schema: - type: string - - name: file_extension - description: Extension of file you want to convert - required: true - options: - - eml - - msg - example: 'eml' - schema: - type: string - - name: parse_email_headers - description: - parameters: - - name: email_headers - description: Email headers - required: true - multiline: true - example: 'Email Headers' - schema: - type: string - returns: - schema: - type: string - returns: - schema: - type: string -large_image: data:image/png;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/hAytodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6Nzg4QTJBMjVEMDI1MTFFN0EwQUVDODc5QjYyQkFCMUQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6Nzg4QTJBMjZEMDI1MTFFN0EwQUVDODc5QjYyQkFCMUQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3ODhBMkEyM0QwMjUxMUU3QTBBRUM4NzlCNjJCQUIxRCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3ODhBMkEyNEQwMjUxMUU3QTBBRUM4NzlCNjJCQUIxRCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pv/bAEMAAwICAgICAwICAgMDAwMEBgQEBAQECAYGBQYJCAoKCQgJCQoM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 diff --git a/email/1.1.0/requirements.txt b/email/1.1.0/requirements.txt deleted file mode 100644 index bf8b6d67..00000000 --- a/email/1.1.0/requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ -requests==2.32.4 -glom==20.11.0 -requests==2.32.4 -eml-parser==1.17.0 -msg-parser==1.2.0 -mail-parser==3.15.0 -extract-msg==0.23.1 -jsonpickle==2.0.0 diff --git a/email/1.1.0/src/app.py b/email/1.1.0/src/app.py deleted file mode 100644 index 6dbac7db..00000000 --- a/email/1.1.0/src/app.py +++ /dev/null @@ -1,360 +0,0 @@ -import json -import uuid -import socket -import asyncio -import requests -import datetime -import base64 -import imaplib -import smtplib -import eml_parser -import time -import random -import eml_parser -import mailparser -import extract_msg -import jsonpickle - -from glom import glom -from msg_parser import MsOxMessage -from email.mime.multipart import MIMEMultipart -from email.mime.text import MIMEText -from email.mime.application import MIMEApplication - -from walkoff_app_sdk.app_base import AppBase - -def json_serial(obj): - if isinstance(obj, datetime.datetime): - serial = obj.isoformat() - return serial - -def default(o): - """helpers to store item in json - arguments: - - o: field of the object to serialize - returns: - - valid serialized value for unserializable fields - """ - if isinstance(o, (datetime.date, datetime.datetime)): - return o.isoformat() - if isinstance(o, set): - return list(o) - if isinstance(o, bytes): - return o.decode("utf-8") - - -class Email(AppBase): - __version__ = "1.1.0" - app_name = "email" - - def __init__(self, redis, logger, console_logger=None): - """ - Each app should have this __init__ to set up Redis and logging. - :param redis: - :param logger: - :param console_logger: - """ - super().__init__(redis, logger, console_logger) - - # This is an email function of Shuffle - def send_email_shuffle(self, apikey, recipients, subject, body): - targets = [recipients] - if ", " in recipients: - targets = recipients.split(", ") - elif "," in recipients: - targets = recipients.split(",") - - data = {"targets": targets, "body": body, "subject": subject, "type": "alert", "email_app": True} - - url = "https://shuffler.io/functions/sendmail" - headers = {"Authorization": "Bearer %s" % apikey} - return requests.post(url, headers=headers, json=data).text - - def send_email_smtp( - self, username, password, smtp_host, recipient, subject, body, smtp_port, attachments="", ssl_verify="True" - ): - if type(smtp_port) == str: - try: - smtp_port = int(smtp_port) - except ValueError: - return "SMTP port needs to be a number (Current: %s)" % smtp_port - - try: - s = smtplib.SMTP(host=smtp_host, port=smtp_port) - except socket.gaierror as e: - return f"Bad SMTP host or port: {e}" - - if ssl_verify == "false" or ssl_verify == "False": - pass - else: - s.starttls() - - if len(username) > 0 or len(password) > 0: - try: - s.login(username, password) - except smtplib.SMTPAuthenticationError as e: - return f"Bad username or password: {e}" - - # setup the parameters of the message - msg = MIMEMultipart() - msg["From"] = username - msg["To"] = recipient - msg["Subject"] = subject - msg.attach(MIMEText(body, "html")) - - # Read the attachments - attachment_count = 0 - try: - if attachments != None and len(attachments) > 0: - print("Got attachments: %s" % attachments) - attachmentsplit = attachments.split(",") - - #attachments = parse_list(attachments, splitter=",") - #print("Got attachments2: %s" % attachmentsplit) - print("Before loop") - files = [] - for file_id in attachmentsplit: - print(f"Looping {file_id}") - file_id = file_id.strip() - new_file = self.get_file(file_id) - print(f"New file: {new_file}") - try: - part = MIMEApplication( - new_file["data"], - Name=new_file["filename"], - ) - part["Content-Disposition"] = f"attachment; filename=\"{new_file['filename']}\"" - msg.attach(part) - attachment_count += 1 - except Exception as e: - print(f"[WARNING] Failed to attach {file_id}: {e}") - - - #files.append(new_file) - - #return files - #data["attachments"] = files - except Exception as e: - print(f"Error in attachment parsing for email: {e}") - - - try: - s.send_message(msg) - except smtplib.SMTPDataError as e: - return { - "success": False, - "reason": f"Failed to send mail: {e}" - } - - print("Successfully sent email with subject %s to %s" % (subject, recipient)) - return { - "success": True, - "reason": "Email sent to %s!" % recipient, - "attachments": attachment_count - } - - def get_emails_imap( - self, - username, - password, - imap_server, - foldername, - amount, - unread, - fields, - include_raw_body, - include_attachment_data, - upload_email_shuffle, - upload_attachments_shuffle, - ssl_verify="True" - ): - def path_to_dict(path, value=None): - def pack(parts): - return ( - {parts[0]: pack(parts[1:]) if len(parts) > 1 else value} - if len(parts) > 1 - else {parts[0]: value} - ) - - return pack(path.split(".")) - - def merge(d1, d2): - for k in d2: - if k in d1 and isinstance(d1[k], dict) and isinstance(d2[k], dict): - merge(d1[k], d2[k]) - else: - d1[k] = d2[k] - - if type(amount) == str: - try: - amount = int(amount) - except ValueError: - return "Amount needs to be a number, not %s" % amount - - try: - email = imaplib.IMAP4_SSL(imap_server) - except ConnectionRefusedError as error: - try: - email = imaplib.IMAP4(imap_server) - - if ssl_verify == "false" or ssl_verify == "False": - pass - else: - email.starttls() - except socket.gaierror as error: - return "Can't connect to IMAP server %s: %s" % (imap_server, error) - except socket.gaierror as error: - return "Can't connect to IMAP server %s: %s" % (imap_server, error) - - try: - email.login(username, password) - except imaplib.IMAP4.error as error: - return "Failed to log into %s: %s" % (username, error) - - email.select(foldername) - unread = True if unread.lower().strip() == "true" else False - try: - # IMAP search queries, e.g. "seen" or "read" - # https://www.rebex.net/secure-mail.net/features/imap-search.aspx - mode = "(UNSEEN)" if unread else "ALL" - thistype, data = email.search(None, mode) - except imaplib.IMAP4.error as error: - return "Couldn't find folder %s." % (foldername) - - email_ids = data[0] - id_list = email_ids.split() - if id_list == None: - return "Couldn't retrieve email. Data: %s" % data - - try: - print("LIST: ", len(id_list)) - except TypeError: - return "Error getting email. Data: %s" % data - - include_raw_body = True if include_raw_body.lower().strip() == "true" else False - include_attachment_data = ( - True if include_attachment_data.lower().strip() == "true" else False - ) - upload_email_shuffle = ( - True if upload_email_shuffle.lower().strip() == "true" else False - ) - upload_attachments_shuffle = ( - True if upload_attachments_shuffle.lower().strip() == "true" else False - ) - - # Convert of mails in json - emails = [] - ep = eml_parser.EmlParser( - include_attachment_data=include_attachment_data - or upload_attachments_shuffle, - include_raw_body=include_raw_body, - ) - try: - for i in range(len(id_list) - 1, len(id_list) - amount - 1, -1): - resp, data = email.fetch(id_list[i], "(RFC822)") - error = None - - if resp != "OK": - print("Failed getting %s" % id_list[i]) - continue - - if data == None: - continue - - output_dict = {} - parsed_eml = ep.decode_email_bytes(data[0][1]) - - if fields and fields.strip() != "": - for field in fields.split(","): - field = field.strip() - merge( - output_dict, - path_to_dict( - field, - glom(parsed_eml, field, default=None), - ), - ) - else: - output_dict = parsed_eml - - # Add message-id as top returned field - output_dict["message-id"] = parsed_eml["header"]["header"][ - "message-id" - ][0] - - if upload_email_shuffle: - email_up = [{"filename": "email.msg", "data": data[0][1]}] - email_id = self.set_files(email_up) - output_dict["email_uid"] = email_id[0] - - if upload_attachments_shuffle: - atts_up = [ - { - "filename": x["filename"], - "data": base64.b64decode(x["raw"]), - } - for x in parsed_eml["attachment"] - ] - atts_ids = self.set_files(atts_up) - output_dict["attachments_uids"] = atts_ids - - emails.append(output_dict) - except Exception as err: - return "Error during email processing: {}".format(err) - return json.dumps(emails, default=default) - - def parse_email_file(self, file_id, file_extension): - file_path = self.get_file(file_id) - if file_path["success"] == False: - return { - "success": False, - "reason": "Couldn't get file with ID %s" % file_id - } - - print("File: %s" % file_path) - if file_extension.lower() == 'eml': - print('working with .eml file') - ep = eml_parser.EmlParser() - try: - parsed_eml = ep.decode_email_bytes(file_path['data']) - return json.dumps(parsed_eml, default=json_serial) - except Exception as e: - return {"Success":"False","Message":f"Exception occured: {e}"} - elif file_extension.lower() == 'msg': - print('working with .msg file') - try: - msg = MsOxMessage(file_path['data']) - msg_properties_dict = msg.get_properties() - print(msg_properties_dict) - frozen = jsonpickle.encode(msg_properties_dict) - return frozen - except Exception as e: - return {"Success":"False","Message":f"Exception occured: {e}"} - else: - return {"Success":"False","Message":f"No file handler for file extension {file_extension}"} - - def parse_email_headers(self, email_headers): - try: - email_headers = bytes(email_headers,'utf-8') - ep = eml_parser.EmlParser() - parsed_headers = ep.decode_email_bytes(email_headers) - return json.dumps(parsed_headers, default=json_serial) - except Exception as e: - raise Exception(e) - - -# Run the actual thing after we've checked params -def run(request): - action = request.get_json() - authorization_key = action.get("authorization") - current_execution_id = action.get("execution_id") - - if action and "name" in action and "app_name" in action: - Email.run(action) - return f'Attempting to execute function {action["name"]} in app {action["app_name"]}' - else: - return f"Invalid action" - - -if __name__ == "__main__": - Email.run() diff --git a/email/1.2.0/Dockerfile b/email/1.2.0/Dockerfile deleted file mode 100644 index bcc1273d..00000000 --- a/email/1.2.0/Dockerfile +++ /dev/null @@ -1,27 +0,0 @@ -# Base our app image off of the WALKOFF App SDK image -FROM frikky/shuffle:app_sdk as base - -# We're going to stage away all of the bloat from the build tools so lets create a builder stage -FROM base as builder - -# Install all alpine build tools needed for our pip installs -RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev - -# Install all of our pip packages in a single directory that we can copy to our base image later -RUN mkdir /install -WORKDIR /install -COPY requirements.txt /requirements.txt -RUN pip install --prefix="/install" -r /requirements.txt - -# Switch back to our base image and copy in all of our built packages and source code -FROM base -COPY --from=builder /install /usr/local -COPY src /app - -# Install any binary dependencies needed in our final image -# RUN apk --no-cache add --update my_binary_dependency - -# Finally, lets run our app! -WORKDIR /app -CMD python app.py --log-level DEBUG - diff --git a/email/1.2.0/api.yaml b/email/1.2.0/api.yaml deleted file mode 100644 index d19e7e7b..00000000 --- a/email/1.2.0/api.yaml +++ /dev/null @@ -1,280 +0,0 @@ -walkoff_version: 1.2.0 -app_version: 1.2.0 -name: email -description: Email app -tags: - - email -categories: - - communication -contact_info: - name: "@frikkylikeme" - url: https://github.com/frikky - email: "frikky@shuffler.io" -actions: - - name: send_email_shuffle - description: Send an email from Shuffle - parameters: - - name: apikey - description: Your https://shuffler.io apikey - multiline: false - example: "https://shuffler.io apikey" - required: true - schema: - type: string - - name: recipients - description: The recipients of the email - multiline: false - example: "test@example.com,frikky@shuffler.io" - required: true - schema: - type: string - - name: subject - description: The subject to use - multiline: false - example: "SOS this is an alert :o" - required: true - schema: - type: string - - name: body - description: The body to add to the email - multiline: true - example: "This is an email alert from Shuffler.io :)" - required: true - schema: - type: string - returns: - schema: - type: string - - name: send_email_smtp - description: Send an email with SMTP - parameters: - - name: username - description: The SMTP login username - multiline: false - example: "frikky@shuffler.io" - required: true - schema: - type: string - - name: password - description: The password to log in with SMTP - multiline: false - example: "******************" - required: true - schema: - type: string - - name: smtp_host - description: The host of the SMTP - multiline: false - example: "smtp-mail.outlook.com" - required: true - schema: - type: string - - name: smtp_port - description: The port to use for SMTP - multiline: false - example: "587" - required: true - schema: - type: string - - name: recipient - description: The receiver(s) of the email - multiline: false - example: "frikky@shuffler.io,frikky@shuffler.io" - required: true - schema: - type: string - - name: subject - description: The subject of the email - multiline: false - example: "This is a subject, hello there :)" - required: true - schema: - type: string - - name: body - description: The body to add to the email - multiline: true - example: "This is an email alert from Shuffler.io :)" - required: true - schema: - type: string - - name: attachments - description: Send files from shuffle as part of the email - multiline: false - example: "file_id1,file_id2,file_id3" - required: false - schema: - type: string - - name: ssl_verify - description: Whether to use TLS or not - example: "true" - required: false - options: - - true - - false - schema: - type: string - - name: body_type - description: The type of body to send. HTML by default - example: "true" - required: false - options: - - "html" - - "plain" - schema: - type: string - returns: - schema: - type: string - - name: get_emails_imap - description: Get emails using IMAP (e.g. imap.gmail.com / Outlook.office365.com) - parameters: - - name: username - description: The SMTP login username - multiline: false - example: "frikky@shuffler.io" - required: true - schema: - type: string - - name: password - description: The password to log in with SMTP - multiline: false - example: "******************" - required: true - schema: - type: string - - name: imap_server - description: The imap server host - multiline: false - example: "Outlook.office365.com" - required: true - schema: - type: string - - name: foldername - description: The folder to use, e.g. "inbox" - multiline: false - example: "inbox" - required: true - schema: - type: string - - name: amount - description: Amount of emails to retrieve - multiline: false - example: "10" - required: true - schema: - type: string - - name: unread - description: Retrieve just unread emails - multiline: false - options: - - "false" - - "true" - required: true - schema: - type: bool - - name: fields - description: Comma separated list of fields to be exported - multiline: false - example: "body, header.subject, header.header.message-id" - required: false - schema: - type: string - - name: include_raw_body - description: Include raw body in email export - multiline: false - options: - - "true" - - "false" - required: true - schema: - type: bool - - name: include_attachment_data - description: Include raw attachments in email export - multiline: false - options: - - "false" - - "true" - required: true - schema: - type: bool - - name: upload_email_shuffle - description: Upload email in shuffle, return uid - multiline: false - options: - - "false" - - "true" - required: true - schema: - type: bool - - name: upload_attachments_shuffle - description: Upload attachments in shuffle, return uids - multiline: false - options: - - "false" - - "true" - required: true - schema: - type: bool - - name: ssl_verify - description: Whether to use TLS or not - example: "true" - required: false - options: - - true - - false - schema: - type: string - - name: mark_as_read - description: Mark email as read or not - multiline: false - options: - - "false" - - "true" - required: false - schema: - type: bool - - name: parse_email_file - description: Takes a file from shuffle and analyzes it if it's a valid .eml or .msg - parameters: - - name: file_id - description: file id - required: true - multiline: true - example: 'adf5e3d0fd85633be17004735a0a119e' - schema: - type: string - - name: file_extension - description: Extension of file you want to convert - required: true - options: - - eml - - msg - example: 'eml' - schema: - type: string - - name: parse_email_headers - description: - parameters: - - name: email_headers - description: Email headers - required: true - multiline: true - example: 'Email Headers' - schema: - type: string - returns: - schema: - type: string - - name: analyze_headers - description: - parameters: - - name: headers - description: Email headers in any format - required: true - multiline: true - schema: - type: string - returns: - schema: - type: string -large_image: data:image/png;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/hAytodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6Nzg4QTJBMjVEMDI1MTFFN0EwQUVDODc5QjYyQkFCMUQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6Nzg4QTJBMjZEMDI1MTFFN0EwQUVDODc5QjYyQkFCMUQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3ODhBMkEyM0QwMjUxMUU3QTBBRUM4NzlCNjJCQUIxRCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3ODhBMkEyNEQwMjUxMUU3QTBBRUM4NzlCNjJCQUIxRCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pv/bAEMAAwICAgICAwICAgMDAwMEBgQEBAQECAYGBQYJCAoKCQgJCQoMDwwKCw4LCQkNEQ0ODxAQERAKDBITEhATDxAQEP/AAAsIAGQAZAEBEQD/xAAeAAABAwUBAQAAAAAAAAAAAAAAAQgJAgQFBwoGA//EAEoQAAECBAMEBwMDEQgDAAAAAAECAwAEBREGBxIIITFRCRMiMkFSYRRicSNCQxUWGBk2U1dYc3WBlJWzwdLTJDNjZXKDkeEmgqH/2gAIAQEAAD8Ak8JKipSlBwuDSpSeDw8qeRguQQrUAQNAV4JH3s+sA7OnT8n1fcv9Bfzc7wWAAToIAOsJ8Uq++H0gI1XSUlYWdSkji6fMnkBAdS9SidesWUpI3OjyjkYXtXCgbEDSFW3JT5D6+sIOzp0/J9X3NX0F/NzvBYABOggA6wnxSr74fSAjVdJSVhZ1KSOLp8yeQEBJUSVKCysaVKHB0eVPIwXIIVqAIGgK8Ej72fWFS640kNtzjcukcGli6k/GENwVagAQO0EcEjmj1g33AATe1wD3SnmffgG/Tp337mv535T+EaB2k9tzInZilVyuMq+up4iUjWxh+mFLs8s23dbv0stnmsgnwBiNPOHpddozHExMSmWsrSsA0tZIaMs0JudCfV50FKf/AFQPjDYsT7S+0LjJ8zGJc68aTqiSrSqtPoQD6ISoJH6BGFkM5c3qW8Jim5qYvlXArUFNVuZSb89y43Tlv0ju15ltMtKZzUmsRSbZGuSxC0mebcHIrV8r/wALEP02c+l2ywzAmZXDGeND+saqvEIRVWXFP0x1Z3AOE9thPx1JHioQ/un1Kn1eQZqtLn2ZuSmkJdamZVwOIWlQuktKTuUg8xFybgnUACB2gjgkc0e9BvuAAm9rgHulPM+/CpDik3bal1p8FPd8/GEtp7Ojq+r36OPUe96wWv2dF79vR5/8T/qI/ukM6RMZMGcyWyUqDMzjZ5vRWKwmy26UlQ3IQOBmLHx3IFibncIeqvWKtiCqTVbrtSmahUJ11T0zNTLqnHXnFG5UpSiSSeZi0ggggh2GxRt8Y92X69K4cr83N1zLuadCZumLXrcp4Ue0/KXPZPiW+6r0O+JxsF4zwvmFhSl42wXV5eo0Sqy6ZySmWFakJbUO/wDHiCk7wQQd4jN2v2dF79vR5v8AE/6g6rrflPYfar/S69Or9EIAAE6QoAHshfFJ5r9Ibpt3bTrOzBkbPYhpb7f11V5aqZh9le8iZUm65i3i20ntcirQPGIA6nU6jWqlNVirzr05PTzy5iZmHllTjrqyVKWoneSSSSYtoIIIIIIkI6J/axmsA4+Rs84yqZ+tvFb5VQ1vL7EjVCNyN/Bt4C1vOEn5xiYndYghVr3IHeKuY9yKVJbJu63MrV4qY7h+EVA6rEKKwvclSuLp8quQiDnpWM5ZnMrafn8HS04pykZfy6KMw2D2BNEByZUPXWQj/aEM0h3uwvk5PYmoeLM4HcnqHmth/CU1KytdwrNy5M+uUdQtZmZBYIu83oN2z3wbcbWk7ym2Zej6zuwXJ49y5yXwbUqXNgpUPZlpelnh32XmyrU24k7ik7/0WMey+wI2OPxesJfqyv5oPsCNjj8XrCX6sr+aD7AjY4/F6wl+rq/mhs2b2SGzXjPGc3s9bKuzngiq4zZGjEWJ3pRTlKwkyrcVOKCrOzVr6GRex73AiIe65TvqRWqhSet632Kadl9enTq0LKb28L24R86ZUp6jVKUrFMmVy85IvtzMu8g2U24hQUlQPMEAx0h7O+abGdWR+DM0mnAF16lMuzRTxamgNDzQHIOJWI2IpxDZ0Lm3ZdQ4tti6U/CEdeDaHJhxYWAklahwdAF9KeRjmXzRxJMYxzLxXiyacUt2sVqdnlFRuflHlq/jHmIlv6D37is1T/mlM/cvQ5zNnZ7xtl/jWc2htlFUtIYrfs7iXCLy+rpeLGk7zcDczN2vpdFrnvcSTsrIPaHwNtBYcmKlhsv02t0h4ydfw7UE9XUKPOJJC2Xmzv3KBAWNyrbvEDaDjjbTa3XVpQhAKlKUbBIHEk+ENLxdnDj/AGr8T1LJ7Zgra6NgymPmSxhmU0LhB+kkaV4OPkGynu6gG4PAlwGUuT2AMjsDy2BMuqGin06XBcdWTrfm3j3333D2nHFHeVH/AOCwjmnxr92Ve/Oc1+9VGGibDogMVP1vZWmKI+4b4dxJOybS1G4S06ht7QPipxf/ADD4kurbGhE21LpHBtwXUn4xbVNpb9OnGAAFrl3EkI4JukgFHrHMFWpdyUrE/KvAhxmZdbUDxBCyDFnEuHQej/wjNU/5rTP3L0PQ2hs1cR4f+pOUWU/VTGZeOtbFK1jW3SZNO6YqkwPBtlJ7IPfcKUi++NdYh2H5LB1CoWLtnXFD2Fs1sLS6rV+ZUXG8SqWouPtVVP0yXnCo6+8gqFtwAGAbkdpzbFcTgXNLB1Qyay9pBEri1iXm9VQxPNo/vJeVdT/dyJ3XcG9YNgTvt6TFODKZsY4nlc2MsaEmRypn25em45oMi2eqpiUANsVllA8gsiYtvUiyzcpJhz8rOylSkGqjITTUzKzTKXmHmlhSHG1C6VJI3EEEEGOXnGv3ZV785zX71UYaJjehfk3mdn/GM4oHRM4sWEBfcsiUZ1Eeu+JBUhxSbttS60+Cnu+fjCAaDbR1fV9rRx6n3vW8c5+2Bl1MZV7TGYmDXmlIaZrkxNyhIsFy0wrr2lD00OJjT0SfdE5mphvJnIrOXHmJutdalatSmZSSlxqmKhNuNOpYlWU8VOOLISAOdzuBiQLZ5yrxJQTVs382Q0/mVjrQ9VAk6m6RJp3y9Llz4NtA9ojvuFSjfdG54N8fGekZOpyUxTajKtTMrNNLYfYdSFIdbULKSpJ3EEEgiG4ZXz07s0Zis7OuJpp1zAmJFvP5cVSYWSJVYut2hurPzkC62Ce83dHFFogGxr92Ve/Oc1+9VGGiezo0MupnLzY/wezPy5bm8RqmMROsqFiUvr+SWf8AaQ2besOl6rrflPYfar/S69Or9EIAAE6QoAHshfFJ5r9Ii76Y7Z4mJgUHaSw7IqWhlCKHiLQN6RcmWmP9Nypsn8mIizj3GWGdOY2T9Xka1gSuJlH6bO/VKWbflm5hlubDam0v9U4lSC4lClBKiLp1G1iY3v8AbSdtr8LTP7Ekf6UL9tJ22vwss/sOR/pQfbSdtr8LLP7Dkf6UH20nba/C0z+w5H+lHmMxOkB2qc1MOKwrjjMNmfkPaGZxrTSZRl1iYZWFtPNOobC21pULhSSDx8DDe5uamJ6aenZt1Tr8w4p11auKlqNyT8SY2ZszZH1raIzqw1ldSG1hqozSXKlMAHTKyLZCn3VHwsi4HvKSPGOjSj0im0CjyVBpMqJen06XalZZhG7Q22kJQE+4AAIulJbJu63MrV4qY7h+EVA6rEKKwvclSuLp8quQjB45wVhrMbB9YwNjGnIn6JW5VyQnWVjihYtoTysbEKHAgGOf7a72U8abKeZszhStMOzVAnVreoNXCfk5uXvuSojcHUAgLTz3jcRGi4IIIIIuqVSqnXKnK0ajSD89PzzyJeWlpdsrcecUbJQlI3kkkAAROd0eGxqjZiy7XiLF8sy5j/FjaFVEiyhJMDeiSB9D2lkbiqw4JEO58CrUQAdJV4pPkHu+sIpxDZ0Lm3ZdQ4tti6U/CFJKiVKUFle5Sk8HR5U8jBcghWoAgaArwSPIfe9Y8PnJkvl1nzgScy7zMoDVQpMyLtlXZekHfmvNucULHgR8DcEiIZtqro1s58gZucxFg6QmsbYJQVOonpFkqnJNrw9pYTcgAfSJuk8Tp4Qz9SSklKgQQbEHwgggjYeTOz9m7n/iFGHMq8Fz1YdCgJiZSjRKSiT8955XYQB6m58AYmN2LejtwJsyoYxri52XxVmC43unA3/ZpAEb0ygVvv4F09ojgEgm7wSSq5Kgsr3KUODo8qeRguQQrUAQNAV4JHkPvesKl1bY0Im2pdI4NuC6k/GENwVagAQO0EcEjmj1g33AATe1wD3SnmffgG/Tp337mv535T+EG4i4KiCbAnvE8j7kaHzf2Hdl/O1+YqONcraexU3jd6p0i8jNFfPU1ZLnxWlUNjxL0LOTs6+tzC+bWLKSkHUWpmXl5xKR4BJAbJjD0/oTcEoeH1Uz5rbzfe0sUZlolH+pTigFelo3Nlr0UuyZgSYZn6vQ6zjKaQQpr6uz3yBI462WQhNvRVxDscMYVwvgujMYfwfh+n0Wly/ZZlJCVQw2k8tCABp9YypsL6iQAe0U8Unkj3YDcE6gAQO0EcEjmj3oN9wAE3tcA90p5n34VIcUm7bUutPgp7vn4wOpS05MNtiyZdAW0PKo+MASkuIbIulbPXKHNfOEa+V9m6zf7Vq633rcIpSoqbQ6T2lvdQo80coVxRbRMLRuVLuBts+VJ4iKnEhtb6ECwl0BxseVR8YAlJcQ2RdK2euUOa+cI18r7N1m/wBq1db71uEUpUVNodJ7S3uoUeaOUK4otomFo3Kl3A22fKk8RFTiQ2t9CBYS6A42PKo+MASkuIbIulbPXKHNfOPtKSkvNy6JiYaC3F71KJO+P//Z diff --git a/email/1.2.0/requirements.txt b/email/1.2.0/requirements.txt deleted file mode 100644 index 0339123c..00000000 --- a/email/1.2.0/requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ -requests==2.32.4 -glom==20.11.0 -eml-parser==1.17.5 -msg-parser==1.2.0 -mail-parser==3.15.0 -extract-msg==0.30.9 -jsonpickle==2.0.0 - diff --git a/email/1.2.0/src/analyze.py b/email/1.2.0/src/analyze.py deleted file mode 100644 index 77e3169e..00000000 --- a/email/1.2.0/src/analyze.py +++ /dev/null @@ -1,158 +0,0 @@ -import json -import eml_parser -import datetime - -def json_serial(obj): - if isinstance(obj, datetime.datetime): - serial = obj.isoformat() - return serial - -# 1. -# "headers": { -# "headername": ["asd"] -# } - -# 2. -# "headers": [ -# "key": "headerame", -# "value": "headervalue" -# ] - -# 3. -# Raw headers - -def parse_email_headers(email_headers): - try: - email_headers = bytes(email_headers,'utf-8') - ep = eml_parser.EmlParser() - parsed_headers = ep.decode_email_bytes(email_headers) - return json.dumps(parsed_headers, default=json_serial) - except Exception as e: - raise Exception(e) - -# Basic function to check headers in an email -# Can be dumped in in pretty much any format -def analyze_headers(headers): - # Raw - if isinstance(headers, str): - headers = parse_email_headers(headers) - if isinstance(headers, str): - headers = json.loads(headers) - - headers = headers["header"]["header"] - - # Just a way to parse out shitty email formats - if "header" in headers: - headers = headers["header"] - if "header" in headers: - headers = headers["header"] - - if not isinstance(headers, list): - newheaders = [] - for key, value in headers.items(): - if isinstance(value, list): - newheaders.append({ - "key": key, - "value": value[0], - }) - else: - newheaders.append({ - "key": key, - "value": value, - }) - - headers = newheaders - - - spf = False - dkim = False - dmarc = False - spoofed = False - - analyzed_headers = { - "success": True, - } - - for item in headers: - if "name" in item: - item["key"] = item["name"] - - item["key"] = item["key"].lower() - - if "spf" in item["key"]: - if "pass " in item["value"].lower(): - spf = True - - if "dkim" in item["key"]: - if "pass " in item["value"].lower(): - dkim = True - - if "dmarc" in item["key"]: - print("dmarc: ", item["key"]) - - if item["key"] == "authentication-results": - if "spf=pass" in item["value"]: - spf = True - if "dkim=pass" in item["value"]: - dkim = True - if "dmarc=pass" in item["value"]: - dmarc = True - - # Fix spoofed! - if item["key"] == "from": - print("From: " + item["value"]) - - if "<" in item["value"]: - item["value"] = item["value"].split("<")[1] - - for subitem in headers: - if "name" in subitem: - subitem["key"] = subitem["name"] - - - subitem["key"] = subitem["key"].lower() - print("Found: ", subitem["key"]) - - if subitem["key"] == "reply-to": - if "<" in subitem["value"]: - subitem["value"] = subitem["value"].split("<")[1] - - print("Reply-To: " + subitem["value"], item["value"]) - if item["value"] != subitem["value"]: - spoofed = True - analyzed_headers["spoofed_reason"] = "Reply-To is different than From" - break - - if subitem["key"] == "mail-reply-to": - if "<" in subitem["value"]: - subitem["value"] = subitem["value"].split("<")[1] - - if item["value"] != subitem["value"]: - spoofed = True - analyzed_headers["spoofed_reason"] = "Mail-Reply-To is different than From" - break - - analyzed_headers["spf"] = spf - analyzed_headers["dkim"] = dkim - analyzed_headers["dmarc"] = dmarc - analyzed_headers["spoofed"] = spoofed - - # Should be a dictionary - return analyzed_headers - - -with open("hdr.txt", "r") as tmp: - data = json.loads(tmp.read()) - print(analyze_headers(data)) - - print() -# -#with open("hdr2.txt", "r") as tmp: -# data = json.loads(tmp.read()) -# print(analyze_headers(data)) -# -# print() -# -#with open("hdr3.txt", "r") as tmp: -# data = tmp.read() -# print(analyze_headers(data)) diff --git a/email/1.2.0/src/app.py b/email/1.2.0/src/app.py deleted file mode 100644 index 700c9890..00000000 --- a/email/1.2.0/src/app.py +++ /dev/null @@ -1,566 +0,0 @@ -import json -import uuid -import socket -import asyncio -import requests -import datetime -import base64 -import imaplib -import smtplib -import time -import random -import eml_parser -import mailparser -import extract_msg -import jsonpickle - -from glom import glom -from msg_parser import MsOxMessage -from email.mime.multipart import MIMEMultipart -from email.mime.text import MIMEText -from email.mime.application import MIMEApplication - -from walkoff_app_sdk.app_base import AppBase - -def json_serial(obj): - if isinstance(obj, datetime.datetime): - serial = obj.isoformat() - return serial - -def default(o): - """helpers to store item in json - arguments: - - o: field of the object to serialize - returns: - - valid serialized value for unserializable fields - """ - if isinstance(o, (datetime.date, datetime.datetime)): - return o.isoformat() - if isinstance(o, set): - return list(o) - if isinstance(o, bytes): - try: - return o.decode("utf-8") - except: - print("Failed parsing utf-8 string") - return o - - -class Email(AppBase): - __version__ = "1.2.0" - app_name = "email" - - def __init__(self, redis, logger, console_logger=None): - """ - Each app should have this __init__ to set up Redis and logging. - :param redis: - :param logger: - :param console_logger: - """ - super().__init__(redis, logger, console_logger) - - # This is an email function of Shuffle - def send_email_shuffle(self, apikey, recipients, subject, body): - targets = [recipients] - if ", " in recipients: - targets = recipients.split(", ") - elif "," in recipients: - targets = recipients.split(",") - - data = {"targets": targets, "body": body, "subject": subject, "type": "alert", "email_app": True} - - url = "https://shuffler.io/functions/sendmail" - headers = {"Authorization": "Bearer %s" % apikey} - return requests.post(url, headers=headers, json=data).text - - def send_email_smtp( - self, username, password, smtp_host, recipient, subject, body, smtp_port, attachments="", ssl_verify="True", body_type="html" - ): - if type(smtp_port) == str: - try: - smtp_port = int(smtp_port) - except ValueError: - return "SMTP port needs to be a number (Current: %s)" % smtp_port - - try: - s = smtplib.SMTP(host=smtp_host, port=smtp_port) - except socket.gaierror as e: - return f"Bad SMTP host or port: {e}" - - # This is not how it should work.. - # Port 465 & 587 = TLS. Sometimes 25. - if ssl_verify == "false" or ssl_verify == "False": - pass - else: - s.starttls() - - if len(username) > 0 or len(password) > 0: - try: - s.login(username, password) - except smtplib.SMTPAuthenticationError as e: - return { - "success": False, - "reason": f"Bad username or password: {e}" - } - - if body_type == "" or len(body_type) < 3: - body_type = "html" - - # setup the parameters of the message - msg = MIMEMultipart() - msg["From"] = username - msg["To"] = recipient - msg["Subject"] = subject - msg.attach(MIMEText(body, body_type)) - - # Read the attachments - attachment_count = 0 - try: - if attachments != None and len(attachments) > 0: - print("Got attachments: %s" % attachments) - attachmentsplit = attachments.split(",") - - #attachments = parse_list(attachments, splitter=",") - #print("Got attachments2: %s" % attachmentsplit) - print("Before loop") - files = [] - for file_id in attachmentsplit: - print(f"Looping {file_id}") - file_id = file_id.strip() - new_file = self.get_file(file_id) - print(f"New file: {new_file}") - try: - part = MIMEApplication( - new_file["data"], - Name=new_file["filename"], - ) - part["Content-Disposition"] = f"attachment; filename=\"{new_file['filename']}\"" - msg.attach(part) - attachment_count += 1 - except Exception as e: - print(f"[WARNING] Failed to attach {file_id}: {e}") - - - #files.append(new_file) - - #return files - #data["attachments"] = files - except Exception as e: - self.logger.info(f"Error in attachment parsing for email: {e}") - - - try: - s.send_message(msg) - except smtplib.SMTPDataError as e: - return { - "success": False, - "reason": f"Failed to send mail: {e}" - } - - self.logger.info("Successfully sent email with subject %s to %s" % (subject, recipient)) - return { - "success": True, - "reason": "Email sent to %s!" % recipient, - "attachments": attachment_count - } - - def get_emails_imap( - self, - username, - password, - imap_server, - foldername, - amount, - unread, - fields, - include_raw_body, - include_attachment_data, - upload_email_shuffle, - upload_attachments_shuffle, - ssl_verify="True", - mark_as_read="False", - ): - def path_to_dict(path, value=None): - def pack(parts): - return ( - {parts[0]: pack(parts[1:]) if len(parts) > 1 else value} - if len(parts) > 1 - else {parts[0]: value} - ) - - return pack(path.split(".")) - - def merge(d1, d2): - for k in d2: - if k in d1 and isinstance(d1[k], dict) and isinstance(d2[k], dict): - merge(d1[k], d2[k]) - else: - d1[k] = d2[k] - - #if isinstance(mark_as_read, str): - # if str(mark_as_read).lower() == "true": - # mark_as_read = True - # else: - # mark_as_read = False - - if type(amount) == str: - try: - amount = int(amount) - except ValueError: - return { - "success": False, - "reason": "Amount needs to be a number, not %s" % amount, - } - - try: - email = imaplib.IMAP4_SSL(imap_server) - except ConnectionRefusedError as error: - try: - email = imaplib.IMAP4(imap_server) - - if ssl_verify == "false" or ssl_verify == "False" or ssl_verify == False: - pass - else: - email.starttls() - except socket.gaierror as error: - return { - "success": False, - "reason": "Can't connect to IMAP server %s: %s" % (imap_server, error), - } - except socket.gaierror as error: - return { - "success": False, - "reason": "Can't connect to IMAP server %s: %s" % (imap_server, error), - } - - try: - email.login(username, password) - except imaplib.IMAP4.error as error: - return { - "success": False, - "reason": "Failed to log into %s: %s" % (username, error), - } - - email.select(foldername) - unread = True if unread.lower().strip() == "true" else False - - try: - # IMAP search queries, e.g. "seen" or "read" - # https://www.rebex.net/secure-mail.net/features/imap-search.aspx - mode = "(UNSEEN)" if unread else "ALL" - thistype, data = email.search(None, mode) - except imaplib.IMAP4.error as error: - return { - "success": False, - "reason": "Couldn't find folder %s." % (foldername), - } - - email_ids = data[0] - id_list = email_ids.split() - if id_list == None: - return { - "success": False, - "reason": f"Couldn't retrieve email. Data: {data}", - } - - #try: - # self.logger.info(f"LIST: {id_list}") - #except TypeError: - # return { - # "success": False, - # "reason": "Error getting email. Data: %s" % data, - # } - - mark_as_read = True if str(mark_as_read).lower().strip() == "true" else False - include_raw_body = True if str(include_raw_body).lower().strip() == "true" else False - include_attachment_data = ( - True if str(include_attachment_data).lower().strip() == "true" else False - ) - upload_email_shuffle = ( - True if str(upload_email_shuffle).lower().strip() == "true" else False - ) - upload_attachments_shuffle = ( - True if str(upload_attachments_shuffle).lower().strip() == "true" else False - ) - - # Convert of mails in json - emails = [] - ep = eml_parser.EmlParser( - include_attachment_data=include_attachment_data - or upload_attachments_shuffle, - include_raw_body=include_raw_body, - ) - - if len(id_list) == 0: - return { - "success": True, - "messages": [], - } - - try: - amount = len(id_list) if len(id_list) 0: - print("Succesfully ran bash!") - item = stdout[0] - else: - print("FAILED to run bash!") - item = stdout[1] - - try: - ret = item.decode("utf-8") - return ret - except: - return item - - return item - - def splitheaders(self, headers): - parsed_headers = {} - if headers: - split_headers = headers.split("\n") - self.logger.info(split_headers) - for header in split_headers: - if ":" in header: - splititem = ":" - elif "=" in header: - splititem = "=" - else: - self.logger.info("Skipping header %s as its invalid" % header) - continue - - splitheader = header.split(splititem) - if len(splitheader) >= 2: - parsed_headers[splitheader[0].strip()] = splititem.join(splitheader[1:]).strip() - else: - self.logger.info("Skipping header %s with split %s cus only one item" % (header, splititem)) - continue - - return parsed_headers - - def checkverify(self, verify): - if str(verify).lower().strip() == "false": - return False - elif verify == None: - return False - elif verify: - return True - elif not verify: - return False - else: - return True - - def checkbody(self, body): - # Indicates json - if isinstance(body, str): - if body.strip().startswith("{"): - body = json.dumps(ast.literal_eval(body)) - - - # Not sure if loading is necessary - # Seemed to work with plain string into data=body too, and not parsed json=body - #try: - # body = json.loads(body) - #except json.decoder.JSONDecodeError as e: - # return body - - return body - else: - return body - - if isinstance(body, dict) or isinstance(body, list): - try: - body = json.dumps(body) - except: - return body - - return body - - def fix_url(self, url): - # Random bugs seen by users - if "hhttp" in url: - url = url.replace("hhttp", "http") - - if "http:/" in url and not "http://" in url: - url = url.replace("http:/", "http://", -1) - if "https:/" in url and not "https://" in url: - url = url.replace("https:/", "https://", -1) - if "http:///" in url: - url = url.replace("http:///", "http://", -1) - if "https:///" in url: - url = url.replace("https:///", "https://", -1) - if not "http://" in url and not "http" in url: - url = f"http://{url}" - - return url - - def return_file(self, requestdata): - filedata = { - "filename": "response.txt", - "data": requestdata, - } - fileret = self.set_files([filedata]) - if len(fileret) == 1: - return {"success": True, "file_id": fileret[0]} - - return fileret - - def prepare_response(self, request): - try: - parsedheaders = {} - for key, value in request.headers.items(): - parsedheaders[key] = value - - cookies = {} - if request.cookies: - for key, value in request.cookies.items(): - cookies[key] = value - - - jsondata = request.text - try: - jsondata = json.loads(jsondata) - except: - pass - - parseddata = { - "status": request.status_code, - "body": jsondata, - "url": request.url, - "headers": parsedheaders, - "cookies":cookies, - "success": True, - } - - return json.dumps(parseddata) - except Exception as e: - print(f"[WARNING] Failed in request: {e}") - return request.text - - def GET(self, url, headers="", username="", password="", verify=True, http_proxy="", https_proxy="", timeout=5, to_file=False): - url = self.fix_url(url) - - parsed_headers = self.splitheaders(headers) - parsed_headers["User-Agent"] = "Shuffle Automation" - verify = self.checkverify(verify) - - proxies = {} - if http_proxy: - proxies["http"] = http_proxy - if https_proxy: - proxies["https"] = https_proxy - - auth=None - if username or password: - # Shouldn't be used if authorization headers exist - if "Authorization" in parsed_headers: - #print("Found authorization - skipping username & pw") - pass - else: - auth = requests.auth.HTTPBasicAuth(username, password) - - if not timeout: - timeout = 5 - if timeout: - timeout = int(timeout) - - if to_file == "true": - to_file = True - else: - to_file = False - - request = requests.get(url, headers=parsed_headers, auth=auth, verify=verify, proxies=proxies, timeout=timeout) - if not to_file: - return self.prepare_response(request) - - return self.return_file(request.text) - - def POST(self, url, headers="", body="", username="", password="", verify=True, http_proxy="", https_proxy="", timeout=5, to_file=False): - url = self.fix_url(url) - - parsed_headers = self.splitheaders(headers) - parsed_headers["User-Agent"] = "Shuffle Automation" - verify = self.checkverify(verify) - body = self.checkbody(body) - proxies = {} - if http_proxy: - proxies["http"] = http_proxy - if https_proxy: - proxies["https"] = https_proxy - - auth=None - if username or password: - # Shouldn't be used if authorization headers exist - if "Authorization" in parsed_headers: - #print("Found authorization - skipping username & pw") - pass - else: - auth = requests.auth.HTTPBasicAuth(username, password) - - if not timeout: - timeout = 5 - if timeout: - timeout = int(timeout) - - if to_file == "true": - to_file = True - else: - to_file = False - - request = requests.post(url, headers=parsed_headers, auth=auth, data=body, verify=verify, proxies=proxies, timeout=timeout) - if not to_file: - return self.prepare_response(request) - - return self.return_file(request.text) - - def PUT(self, url, headers="", body="", username="", password="", verify=True, http_proxy="", https_proxy="", timeout=5, to_file=False): - url = self.fix_url(url) - - parsed_headers = self.splitheaders(headers) - parsed_headers["User-Agent"] = "Shuffle Automation" - verify = self.checkverify(verify) - body = self.checkbody(body) - proxies = {} - if http_proxy: - proxies["http"] = http_proxy - if https_proxy: - proxies["https"] = https_proxy - - - auth=None - if username or password: - # Shouldn't be used if authorization headers exist - if "Authorization" in parsed_headers: - #print("Found authorization - skipping username & pw") - pass - else: - auth = requests.auth.HTTPBasicAuth(username, password) - - if not timeout: - timeout = 5 - if timeout: - timeout = int(timeout) - - if to_file == "true": - to_file = True - else: - to_file = False - - request = requests.put(url, headers=parsed_headers, auth=auth, data=body, verify=verify, proxies=proxies, timeout=timeout) - if not to_file: - return self.prepare_response(request) - - return self.return_file(request.text) - - def PATCH(self, url, headers="", body="", username="", password="", verify=True, http_proxy="", https_proxy="", timeout=5, to_file=False): - url = self.fix_url(url) - - parsed_headers = self.splitheaders(headers) - parsed_headers["User-Agent"] = "Shuffle Automation" - verify = self.checkverify(verify) - body = self.checkbody(body) - proxies = {} - if http_proxy: - proxies["http"] = http_proxy - if https_proxy: - proxies["https"] = https_proxy - - auth=None - if username or password: - # Shouldn't be used if authorization headers exist - if "Authorization" in parsed_headers: - #print("Found authorization - skipping username & pw") - pass - else: - auth = requests.auth.HTTPBasicAuth(username, password) - - if not timeout: - timeout = 5 - if timeout: - timeout = int(timeout) - - if to_file == "true": - to_file = True - else: - to_file = False - - request = requests.patch(url, headers=parsed_headers, data=body, auth=auth, verify=verify, proxies=proxies, timeout=timeout) - if not to_file: - return self.prepare_response(request) - - return self.return_file(request.text) - - def DELETE(self, url, headers="", body="", username="", password="", verify=True, http_proxy="", https_proxy="", timeout=5, to_file=False): - url = self.fix_url(url) - - parsed_headers = self.splitheaders(headers) - parsed_headers["User-Agent"] = "Shuffle Automation" - verify = self.checkverify(verify) - body = self.checkbody(body) - proxies = {} - if http_proxy: - proxies["http"] = http_proxy - if https_proxy: - proxies["https"] = https_proxy - - auth=None - if username or password: - # Shouldn't be used if authorization headers exist - if "Authorization" in parsed_headers: - #print("Found authorization - skipping username & pw") - pass - else: - auth = requests.auth.HTTPBasicAuth(username, password) - - if not timeout: - timeout = 5 - if timeout: - timeout = int(timeout) - - if to_file == "true": - to_file = True - else: - to_file = False - - request = requests.delete(url, headers=parsed_headers, data=body, auth=auth, verify=verify, proxies=proxies, timeout=timeout) - if not to_file: - return self.prepare_response(request) - - return self.return_file(request.text) - - def HEAD(self, url, headers="", body="", username="", password="", verify=True, http_proxy="", https_proxy="", timeout=5, to_file=False): - url = self.fix_url(url) - - parsed_headers = self.splitheaders(headers) - parsed_headers["User-Agent"] = "Shuffle Automation" - verify = self.checkverify(verify) - body = self.checkbody(body) - proxies = {} - if http_proxy: - proxies["http"] = http_proxy - if https_proxy: - proxies["https"] = https_proxy - - auth=None - if username or password: - # Shouldn't be used if authorization headers exist - if "Authorization" in parsed_headers: - #print("Found authorization - skipping username & pw") - pass - else: - auth = requests.auth.HTTPBasicAuth(username, password) - - if not timeout: - timeout = 5 - if timeout: - timeout = int(timeout) - - if to_file == "true": - to_file = True - else: - to_file = False - - request = requests.head(url, headers=parsed_headers, auth=auth, verify=verify, proxies=proxies, timeout=timeout) - if not to_file: - return self.prepare_response(request) - - return self.return_file(request.text) - - def OPTIONS(self, url, headers="", body="", username="", password="", verify=True, http_proxy="", https_proxy="", timeout=5, to_file=False): - url = self.fix_url(url) - - parsed_headers = self.splitheaders(headers) - parsed_headers["User-Agent"] = "Shuffle Automation" - verify = self.checkverify(verify) - body = self.checkbody(body) - proxies = {} - if http_proxy: - proxies["http"] = http_proxy - if https_proxy: - proxies["https"] = https_proxy - - auth=None - if username or password: - # Shouldn't be used if authorization headers exist - if "Authorization" in parsed_headers: - #print("Found authorization - skipping username & pw") - pass - else: - auth = requests.auth.HTTPBasicAuth(username, password) - - if not timeout: - timeout = 5 - - if timeout: - timeout = int(timeout) - - if to_file == "true": - to_file = True - else: - to_file = False - - request = requests.options(url, headers=parsed_headers, auth=auth, verify=verify, proxies=proxies, timeout=timeout) - if not to_file: - return self.prepare_response(request) - - return self.return_file(request.text) - - -# Run the actual thing after we've checked params -def run(request): - action = request.get_json() - authorization_key = action.get("authorization") - current_execution_id = action.get("execution_id") - - if action and "name" in action and "app_name" in action: - HTTP.run(action) - return f'Attempting to execute function {action["name"]} in app {action["app_name"]}' - else: - return f'Invalid action' - -if __name__ == "__main__": - HTTP.run() diff --git a/http/1.1.0/Dockerfile b/http/1.1.0/Dockerfile deleted file mode 100644 index 9bbc5110..00000000 --- a/http/1.1.0/Dockerfile +++ /dev/null @@ -1,27 +0,0 @@ -# Base our app image off of the WALKOFF App SDK image -FROM frikky/shuffle:app_sdk as base - -# We're going to stage away all of the bloat from the build tools so lets create a builder stage -FROM base as builder - -# Install all alpine build tools needed for our pip installs -RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev - -# Install all of our pip packages in a single directory that we can copy to our base image later -RUN mkdir /install -WORKDIR /install -COPY requirements.txt /requirements.txt -RUN pip install --prefix="/install" -r /requirements.txt - -# Switch back to our base image and copy in all of our built packages and source code -FROM base -COPY --from=builder /install /usr/local -COPY src /app -RUN apk add curl - -# Install any binary dependencies needed in our final image -# RUN apk --no-cache add --update my_binary_dependency - -# Finally, lets run our app! -WORKDIR /app -CMD python app.py --log-level DEBUG diff --git a/http/1.1.0/api.yaml b/http/1.1.0/api.yaml deleted file mode 100644 index bd622ff8..00000000 --- a/http/1.1.0/api.yaml +++ /dev/null @@ -1,365 +0,0 @@ -walkoff_version: 1.1.0 -app_version: 1.1.0 -name: http -description: HTTP app -tags: - - Testing - - HTTP -categories: - - Testing - - HTTP -contact_info: - name: "@frikkylikeme" - url: https://github.com/frikky - email: "frikky@shuffler.io" -actions: - - name: GET - description: Runs a GET request towards the specified endpoint - parameters: - - name: url - description: The URL to get - multiline: false - example: "https://example.com" - required: true - schema: - type: string - - name: headers - description: Headers to use - multiline: true - required: false - example: "Authorization: Bearer asd\nContent-Type: application/json" - schema: - type: string - - name: username - description: The username to use - multiline: false - required: false - example: "Username" - schema: - type: string - - name: password - description: The password to use - multiline: false - required: false - example: "*****" - schema: - type: string - - name: verify - description: Check certificate - multiline: false - options: - - false - - true - required: false - example: "false" - schema: - type: bool - returns: - schema: - type: string - example: "404 NOT FOUND" - - name: POST - description: Runs a POST request towards the specified endpoint - parameters: - - name: url - description: The URL to post to - multiline: false - example: "https://example.com" - required: true - schema: - type: string - - name: body - description: The body to use - multiline: true - example: "{\n\t'json': 'blob'\n}" - required: false - schema: - type: string - - name: headers - description: Headers to use - multiline: true - required: false - example: "Authorization: Bearer asd\nContent-Type: application/json" - schema: - type: string - - name: username - description: The username to use - multiline: false - required: false - example: "Username" - schema: - type: string - - name: password - description: The password to use - multiline: false - required: false - example: "*****" - schema: - type: string - - name: verify - description: Whether to check the certificate or not - multiline: false - required: false - options: - - false - - true - example: "false" - schema: - type: bool - returns: - schema: - type: string - example: "404 NOT FOUND" - - name: PATCH - description: Runs a PATCHrequest towards the specified endpoint - parameters: - - name: url - description: The URL to post to - multiline: false - example: "https://example.com" - required: true - schema: - type: string - - name: body - description: The body to use - multiline: true - example: "{\n\t'json': 'blob'\n}" - required: false - schema: - type: string - - name: headers - description: Headers to use - multiline: true - required: false - example: "Authorization: Bearer asd\nContent-Type: application/json" - schema: - type: string - - name: username - description: The username to use - multiline: false - required: false - example: "Username" - schema: - type: string - - name: password - description: The password to use - multiline: false - required: false - example: "*****" - schema: - type: string - - name: verify - description: Whether to check the certificate or not - multiline: false - required: false - options: - - false - - true - example: "false" - schema: - type: bool - returns: - schema: - type: string - example: "404 NOT FOUND" - - name: PUT - description: Runs a PUT request towards the specified endpoint - parameters: - - name: url - description: The URL to PUT to - multiline: false - example: "https://example.com" - required: true - schema: - type: string - - name: body - description: The body to use - multiline: true - example: "{\n\t'json': 'blob'\n}" - required: false - schema: - type: string - - name: headers - description: Headers to use - multiline: true - required: false - example: "Authorization: Bearer asd\nContent-Type: application/json" - schema: - type: string - - name: username - description: The username to use - multiline: false - required: false - example: "Username" - schema: - type: string - - name: password - description: The password to use - multiline: false - required: false - example: "*****" - schema: - type: string - - name: verify - description: Whether to check the certificate or not - multiline: false - required: false - options: - - false - - true - example: "false" - schema: - type: bool - returns: - schema: - type: string - example: "404 NOT FOUND" - - name: DELETE - description: Runs a DELETE request towards the specified endpoint - parameters: - - name: url - description: The URL to post to - multiline: false - example: "https://example.com" - required: true - schema: - type: string - - name: headers - description: Headers to use - multiline: true - required: false - example: "Authorization: Bearer asd\nContent-Type: application/json" - schema: - type: string - - name: username - description: The username to use - multiline: false - required: false - example: "Username" - schema: - type: string - - name: password - description: The password to use - multiline: false - required: false - example: "*****" - schema: - type: string - - name: verify - description: Whether to check the certificate or not - multiline: false - required: false - options: - - true - - false - example: "false" - schema: - type: bool - returns: - schema: - type: string - example: "404 NOT FOUND" - - name: HEAD - description: Runs a HEAD request towards the specified endpoint - parameters: - - name: url - description: The URL to HEAD to - multiline: false - example: "https://example.com" - required: true - schema: - type: string - - name: headers - description: Headers to use - multiline: true - required: false - example: "Authorization: Bearer asd\nContent-Type: application/json" - schema: - type: string - - name: username - description: The username to use - multiline: false - required: false - example: "Username" - schema: - type: string - - name: password - description: The password to use - multiline: false - required: false - example: "*****" - schema: - type: string - - name: verify - description: Whether to check the certificate or not - multiline: false - required: false - options: - - false - - true - example: "false" - schema: - type: bool - returns: - schema: - type: string - example: "404 NOT FOUND" - - name: OPTIONS - description: Runs a OPTIONS request towards the specified endpoint - parameters: - - name: url - description: The URL to HEAD to - multiline: false - example: "https://example.com" - required: true - schema: - type: string - - name: headers - description: Headers to use - multiline: true - required: false - example: "Authorization: Bearer asd\nContent-Type: application/json" - schema: - type: string - - name: username - description: The username to use - multiline: false - required: false - example: "Username" - schema: - type: string - - name: password - description: The password to use - multiline: false - required: false - example: "*****" - schema: - type: string - - name: verify - description: Whether to check the certificate or not - multiline: false - required: false - options: - - false - - true - example: "false" - schema: - type: bool - returns: - schema: - type: string - example: "404 NOT FOUND" - - name: curl - description: Run a curl command - parameters: - - name: statement - description: The curl command to run - multiline: true - example: "curl https://example.com" - required: true - schema: - type: string - returns: - schema: - type: string -large_image: data:image/png;base64,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 diff --git a/http/1.1.0/requirements.txt b/http/1.1.0/requirements.txt deleted file mode 100644 index 923ac71f..00000000 --- a/http/1.1.0/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -uncurl==0.0.10 -requests==2.32.4 \ No newline at end of file diff --git a/http/1.1.0/src/app.py b/http/1.1.0/src/app.py deleted file mode 100755 index 6b7fcc87..00000000 --- a/http/1.1.0/src/app.py +++ /dev/null @@ -1,247 +0,0 @@ -import time -import json -import ast -import random -import socket -import uncurl -import asyncio -import requests -import subprocess - -from walkoff_app_sdk.app_base import AppBase - -class HTTP(AppBase): - __version__ = "1.0.0" - app_name = "http" - - def __init__(self, redis, logger, console_logger=None): - print("INIT") - """ - Each app should have this __init__ to set up Redis and logging. - :param redis: - :param logger: - :param console_logger: - """ - super().__init__(redis, logger, console_logger) - - # This is dangerously fun :) - # Do we care about arbitrary code execution here? - def curl(self, statement): - process = subprocess.Popen(statement, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, shell=True) - stdout = process.communicate() - item = "" - if len(stdout[0]) > 0: - print("Succesfully ran bash!") - item = stdout[0] - else: - print("FAILED to run bash!") - item = stdout[1] - - try: - ret = item.decode("utf-8") - return ret - except: - return item - - return item - #try: - # if not statement.startswith("curl "): - # statement = "curl %s" % statement - - # data = uncurl.parse(statement) - # request = eval(data) - # if isinstance(request, requests.models.Response): - # return request.text - # else: - # return "Unable to parse the curl parameter. Remember to start with curl " - #except: - # return "An error occurred during curl parsing" - - def splitheaders(self, headers): - parsed_headers = {} - if headers: - split_headers = headers.split("\n") - self.logger.info(split_headers) - for header in split_headers: - if ": " in header: - splititem = ": " - elif ":" in header: - splititem = ":" - elif "= " in header: - splititem = "= " - elif "=" in header: - splititem = "=" - else: - self.logger.info("Skipping header %s as its invalid" % header) - continue - - splitheader = header.split(splititem) - if len(splitheader) == 2: - parsed_headers[splitheader[0]] = splitheader[1] - else: - self.logger.info("Skipping header %s with split %s cus only one item" % (header, splititem)) - continue - - return parsed_headers - - def checkverify(self, verify): - if verify == None: - return False - elif verify: - return True - elif not verify: - return False - elif verify.lower().strip() == "false": - return False - else: - return True - - def checkbody(self, body): - # Indicates json - if body.strip().startswith("{"): - body = json.dumps(ast.literal_eval(body)) - - # Not sure if loading is necessary - # Seemed to work with plain string into data=body too, and not parsed json=body - #try: - # body = json.loads(body) - #except json.decoder.JSONDecodeError as e: - # return body - - return body - else: - return body - - def fix_url(self, url): - # Random bugs seen by users - if "hhttp" in url: - url = url.replace("hhttp", "http") - - if "http:/" in url and not "http://" in url: - url = url.replace("http:/", "http://", -1) - if "https:/" in url and not "https://" in url: - url = url.replace("https:/", "https://", -1) - if "http:///" in url: - url = url.replace("http:///", "http://", -1) - if "https:///" in url: - url = url.replace("https:///", "https://", -1) - if not "http://" in url and not "http" in url: - url = f"http://{url}" - - return url - - def GET(self, url, headers="", username="", password="", verify=True): - url = self.fix_url(url) - - parsed_headers = self.splitheaders(headers) - parsed_headers["User-Agent"] = "Shuffle Automation" - verify = self.checkverify(verify) - - auth=None - if username or password: - auth = requests.auth.HTTPBasicAuth(username, password) - - return requests.get(url, headers=parsed_headers, auth=auth, verify=verify).text - - def POST(self, url, headers="", body="", username="", password="", verify=True): - url = self.fix_url(url) - - parsed_headers = self.splitheaders(headers) - parsed_headers["User-Agent"] = "Shuffle Automation" - verify = self.checkverify(verify) - body = self.checkbody(body) - - auth=None - if username or password: - auth = requests.auth.HTTPBasicAuth(username, password) - - return requests.post(url, headers=parsed_headers, auth=auth, data=body, verify=verify).text - - # UNTESTED BELOW HERE - def PUT(self, url, headers="", body="", username="", password="", verify=True): - url = self.fix_url(url) - - parsed_headers = self.splitheaders(headers) - parsed_headers["User-Agent"] = "Shuffle Automation" - verify = self.checkverify(verify) - body = self.checkbody(body) - - auth=None - if username or password: - auth = requests.auth.HTTPBasicAuth(username, password) - - return requests.put(url, headers=parsed_headers, auth=auth, data=body, verify=verify).text - - def PATCH(self, url, headers="", body="", username="", password="", verify=True): - url = self.fix_url(url) - - parsed_headers = self.splitheaders(headers) - parsed_headers["User-Agent"] = "Shuffle Automation" - verify = self.checkverify(verify) - body = self.checkbody(body) - - auth=None - if username or password: - auth = requests.auth.HTTPBasicAuth(username, password) - - return requests.patch(url, headers=parsed_headers, data=body, auth=auth, verify=verify).text - - def DELETE(self, url, headers="", body="", username="", password="", verify=True): - url = self.fix_url(url) - - parsed_headers = self.splitheaders(headers) - parsed_headers["User-Agent"] = "Shuffle Automation" - verify = self.checkverify(verify) - - auth=None - if username or password: - auth = requests.auth.HTTPBasicAuth(username, password) - - return requests.delete(url, headers=parsed_headers, auth=auth, verify=verify).text - - def HEAD(self, url, headers="", body="", username="", password="", verify=True): - url = self.fix_url(url) - - parsed_headers = self.splitheaders(headers) - parsed_headers["User-Agent"] = "Shuffle Automation" - verify = self.checkverify(verify) - body = self.checkbody(body) - - auth=None - if username or password: - auth = requests.auth.HTTPBasicAuth(username, password) - - return requests.head(url, headers=parsed_headers, auth=auth, verify=verify).text - - def OPTIONS(self, url, headers="", body="", username="", password="", verify=True): - url = self.fix_url(url) - - parsed_headers = self.splitheaders(headers) - parsed_headers["User-Agent"] = "Shuffle Automation" - verify = self.checkverify(verify) - body = self.checkbody(body) - - auth=None - if username or password: - auth = requests.auth.HTTPBasicAuth(username, password) - - return requests.options(url, headers=parsed_headers, auth=auth, verify=verify).text - - -# Run the actual thing after we've checked params -def run(request): - print("Starting cloud!") - action = request.get_json() - print(action) - print(type(action)) - authorization_key = action.get("authorization") - current_execution_id = action.get("execution_id") - - if action and "name" in action and "app_name" in action: - HTTP.run(action) - return f'Attempting to execute function {action["name"]} in app {action["app_name"]}' - else: - return f'Invalid action' - -if __name__ == "__main__": - HTTP.run() diff --git a/http/1.2.0/Dockerfile b/http/1.2.0/Dockerfile deleted file mode 100644 index 9bbc5110..00000000 --- a/http/1.2.0/Dockerfile +++ /dev/null @@ -1,27 +0,0 @@ -# Base our app image off of the WALKOFF App SDK image -FROM frikky/shuffle:app_sdk as base - -# We're going to stage away all of the bloat from the build tools so lets create a builder stage -FROM base as builder - -# Install all alpine build tools needed for our pip installs -RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev - -# Install all of our pip packages in a single directory that we can copy to our base image later -RUN mkdir /install -WORKDIR /install -COPY requirements.txt /requirements.txt -RUN pip install --prefix="/install" -r /requirements.txt - -# Switch back to our base image and copy in all of our built packages and source code -FROM base -COPY --from=builder /install /usr/local -COPY src /app -RUN apk add curl - -# Install any binary dependencies needed in our final image -# RUN apk --no-cache add --update my_binary_dependency - -# Finally, lets run our app! -WORKDIR /app -CMD python app.py --log-level DEBUG diff --git a/http/1.2.0/api.yaml b/http/1.2.0/api.yaml deleted file mode 100644 index 65e22188..00000000 --- a/http/1.2.0/api.yaml +++ /dev/null @@ -1,522 +0,0 @@ -walkoff_version: 1.2.0 -app_version: 1.2.0 -name: http -description: HTTP app -tags: - - Testing - - HTTP -categories: - - Testing - - HTTP -contact_info: - name: "@frikkylikeme" - url: https://github.com/frikky - email: "frikky@shuffler.io" -actions: - - name: GET - description: Runs a GET request towards the specified endpoint - parameters: - - name: url - description: The URL to get - multiline: false - example: "https://example.com" - required: true - schema: - type: string - - name: headers - description: Headers to use - multiline: true - required: false - example: "Authorization: Bearer asd\nContent-Type: application/json" - schema: - type: string - - name: username - description: The username to use - multiline: false - required: false - example: "Username" - schema: - type: string - - name: password - description: The password to use - multiline: false - required: false - example: "*****" - schema: - type: string - - name: verify - description: Check certificate - multiline: false - options: - - false - - true - required: false - example: "false" - schema: - type: bool - - name: http_proxy - description: Add a HTTP proxy - multiline: false - required: false - example: "http://192.168.0.1:8080" - schema: - type: bool - - name: https_proxy - description: Add a HTTPS proxy - multiline: false - required: false - example: "http://192.168.0.1:8080" - schema: - type: bool - - name: timeout - description: Add a timeout for the request, in seconds - multiline: false - required: false - example: "10" - schema: - type: bool - - name: to_file - description: Makes the response into a file, and returns it as an ID - multiline: false - required: false - options: - - false - - true - example: "true" - schema: - type: bool - returns: - schema: - type: string - example: "404 NOT FOUND" - - name: POST - description: Runs a POST request towards the specified endpoint - parameters: - - name: url - description: The URL to post to - multiline: false - example: "https://example.com" - required: true - schema: - type: string - - name: body - description: The body to use - multiline: true - example: "{\n\t'json': 'blob'\n}" - required: false - schema: - type: string - - name: headers - description: Headers to use - multiline: true - required: false - example: "Authorization: Bearer asd\nContent-Type: application/json" - schema: - type: string - - name: username - description: The username to use - multiline: false - required: false - example: "Username" - schema: - type: string - - name: password - description: The password to use - multiline: false - required: false - example: "*****" - schema: - type: string - - name: verify - description: Whether to check the certificate or not - multiline: false - required: false - options: - - false - - true - example: "false" - schema: - type: bool - - name: http_proxy - description: Add a HTTP proxy - multiline: false - required: false - example: "http://192.168.0.1:8080" - schema: - type: bool - - name: https_proxy - description: Add a HTTPS proxy - multiline: false - required: false - example: "http://192.168.0.1:8080" - schema: - type: bool - - name: timeout - description: Add a timeout for the request, in seconds - multiline: false - required: false - example: "10" - schema: - type: bool - returns: - schema: - type: string - example: "404 NOT FOUND" - - name: PATCH - description: Runs a PATCHrequest towards the specified endpoint - parameters: - - name: url - description: The URL to post to - multiline: false - example: "https://example.com" - required: true - schema: - type: string - - name: body - description: The body to use - multiline: true - example: "{\n\t'json': 'blob'\n}" - required: false - schema: - type: string - - name: headers - description: Headers to use - multiline: true - required: false - example: "Authorization: Bearer asd\nContent-Type: application/json" - schema: - type: string - - name: username - description: The username to use - multiline: false - required: false - example: "Username" - schema: - type: string - - name: password - description: The password to use - multiline: false - required: false - example: "*****" - schema: - type: string - - name: verify - description: Whether to check the certificate or not - multiline: false - required: false - options: - - false - - true - example: "false" - schema: - type: bool - - name: http_proxy - description: Add a HTTP proxy - multiline: false - required: false - example: "http://192.168.0.1:8080" - schema: - type: bool - - name: https_proxy - description: Add a HTTPS proxy - multiline: false - required: false - example: "http://192.168.0.1:8080" - schema: - type: bool - - name: timeout - description: Add a timeout for the request, in seconds - multiline: false - required: false - example: "10" - schema: - type: bool - returns: - schema: - type: string - example: "404 NOT FOUND" - - name: PUT - description: Runs a PUT request towards the specified endpoint - parameters: - - name: url - description: The URL to PUT to - multiline: false - example: "https://example.com" - required: true - schema: - type: string - - name: body - description: The body to use - multiline: true - example: "{\n\t'json': 'blob'\n}" - required: false - schema: - type: string - - name: headers - description: Headers to use - multiline: true - required: false - example: "Authorization: Bearer asd\nContent-Type: application/json" - schema: - type: string - - name: username - description: The username to use - multiline: false - required: false - example: "Username" - schema: - type: string - - name: password - description: The password to use - multiline: false - required: false - example: "*****" - schema: - type: string - - name: verify - description: Whether to check the certificate or not - multiline: false - required: false - options: - - false - - true - example: "false" - schema: - type: bool - - name: http_proxy - description: Add a HTTP proxy - multiline: false - required: false - example: "http://192.168.0.1:8080" - schema: - type: bool - - name: https_proxy - description: Add a HTTPS proxy - multiline: false - required: false - example: "http://192.168.0.1:8080" - schema: - type: bool - - name: timeout - description: Add a timeout for the request, in seconds - multiline: false - required: false - example: "10" - schema: - type: bool - returns: - schema: - type: string - example: "404 NOT FOUND" - - name: DELETE - description: Runs a DELETE request towards the specified endpoint - parameters: - - name: url - description: The URL to post to - multiline: false - example: "https://example.com" - required: true - schema: - type: string - - name: headers - description: Headers to use - multiline: true - required: false - example: "Authorization: Bearer asd\nContent-Type: application/json" - schema: - type: string - - name: username - description: The username to use - multiline: false - required: false - example: "Username" - schema: - type: string - - name: password - description: The password to use - multiline: false - required: false - example: "*****" - schema: - type: string - - name: verify - description: Whether to check the certificate or not - multiline: false - required: false - options: - - true - - false - example: "false" - schema: - type: bool - - name: http_proxy - description: Add a HTTP proxy - multiline: false - required: false - example: "http://192.168.0.1:8080" - schema: - type: bool - - name: https_proxy - description: Add a HTTPS proxy - multiline: false - required: false - example: "http://192.168.0.1:8080" - schema: - type: bool - - name: timeout - description: Add a timeout for the request, in seconds - multiline: false - required: false - example: "10" - schema: - type: bool - returns: - schema: - type: string - example: "404 NOT FOUND" - - name: HEAD - description: Runs a HEAD request towards the specified endpoint - parameters: - - name: url - description: The URL to HEAD to - multiline: false - example: "https://example.com" - required: true - schema: - type: string - - name: headers - description: Headers to use - multiline: true - required: false - example: "Authorization: Bearer asd\nContent-Type: application/json" - schema: - type: string - - name: username - description: The username to use - multiline: false - required: false - example: "Username" - schema: - type: string - - name: password - description: The password to use - multiline: false - required: false - example: "*****" - schema: - type: string - - name: verify - description: Whether to check the certificate or not - multiline: false - required: false - options: - - false - - true - example: "false" - schema: - type: bool - - name: http_proxy - description: Add a HTTP proxy - multiline: false - required: false - example: "http://192.168.0.1:8080" - schema: - type: bool - - name: https_proxy - description: Add a HTTPS proxy - multiline: false - required: false - example: "http://192.168.0.1:8080" - schema: - type: bool - - name: timeout - description: Add a timeout for the request, in seconds - multiline: false - required: false - example: "10" - schema: - type: bool - returns: - schema: - type: string - example: "404 NOT FOUND" - - name: OPTIONS - description: Runs a OPTIONS request towards the specified endpoint - parameters: - - name: url - description: The URL to HEAD to - multiline: false - example: "https://example.com" - required: true - schema: - type: string - - name: headers - description: Headers to use - multiline: true - required: false - example: "Authorization: Bearer asd\nContent-Type: application/json" - schema: - type: string - - name: username - description: The username to use - multiline: false - required: false - example: "Username" - schema: - type: string - - name: password - description: The password to use - multiline: false - required: false - example: "*****" - schema: - type: string - - name: verify - description: Whether to check the certificate or not - multiline: false - required: false - options: - - false - - true - example: "false" - schema: - type: bool - - name: http_proxy - description: Add a HTTP proxy - multiline: false - required: false - example: "http://192.168.0.1:8080" - schema: - type: bool - - name: https_proxy - description: Add a HTTPS proxy - multiline: false - required: false - example: "http://192.168.0.1:8080" - schema: - type: bool - - name: timeout - description: Add a timeout for the request, in seconds - multiline: false - required: false - example: "10" - schema: - type: bool - returns: - schema: - type: string - example: "404 NOT FOUND" - - name: curl - description: Run a curl command - parameters: - - name: statement - description: The curl command to run - multiline: true - example: "curl https://example.com" - required: true - schema: - type: string - returns: - schema: - type: string -large_image: data:image/png;base64,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 diff --git a/http/1.2.0/requirements.txt b/http/1.2.0/requirements.txt deleted file mode 100644 index 923ac71f..00000000 --- a/http/1.2.0/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -uncurl==0.0.10 -requests==2.32.4 \ No newline at end of file diff --git a/http/1.2.0/src/app.py b/http/1.2.0/src/app.py deleted file mode 100755 index de56d29c..00000000 --- a/http/1.2.0/src/app.py +++ /dev/null @@ -1,403 +0,0 @@ -import time -import json -import ast -import random -import socket -import uncurl -import asyncio -import requests -import subprocess - -from walkoff_app_sdk.app_base import AppBase - -class HTTP(AppBase): - __version__ = "1.2.0" - app_name = "http" - - def __init__(self, redis, logger, console_logger=None): - print("INIT") - """ - Each app should have this __init__ to set up Redis and logging. - :param redis: - :param logger: - :param console_logger: - """ - super().__init__(redis, logger, console_logger) - - # This is dangerously fun :) - # Do we care about arbitrary code execution here? - def curl(self, statement): - process = subprocess.Popen(statement, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, shell=True) - stdout = process.communicate() - item = "" - if len(stdout[0]) > 0: - print("Succesfully ran bash!") - item = stdout[0] - else: - print("FAILED to run bash!") - item = stdout[1] - - try: - ret = item.decode("utf-8") - return ret - except: - return item - - return item - #try: - # if not statement.startswith("curl "): - # statement = "curl %s" % statement - - # data = uncurl.parse(statement) - # request = eval(data) - # if isinstance(request, requests.models.Response): - # return request.text - # else: - # return "Unable to parse the curl parameter. Remember to start with curl " - #except: - # return "An error occurred during curl parsing" - - def splitheaders(self, headers): - parsed_headers = {} - if headers: - split_headers = headers.split("\n") - self.logger.info(split_headers) - for header in split_headers: - if ": " in header: - splititem = ": " - elif ":" in header: - splititem = ":" - elif "= " in header: - splititem = "= " - elif "=" in header: - splititem = "=" - else: - self.logger.info("Skipping header %s as its invalid" % header) - continue - - splitheader = header.split(splititem) - if len(splitheader) == 2: - parsed_headers[splitheader[0]] = splitheader[1] - else: - self.logger.info("Skipping header %s with split %s cus only one item" % (header, splititem)) - continue - - return parsed_headers - - def checkverify(self, verify): - if verify == None: - return False - elif verify: - return True - elif not verify: - return False - elif verify.lower().strip() == "false": - return False - else: - return True - - def checkbody(self, body): - # Indicates json - if isinstance(body, str): - if body.strip().startswith("{"): - body = json.dumps(ast.literal_eval(body)) - - - # Not sure if loading is necessary - # Seemed to work with plain string into data=body too, and not parsed json=body - #try: - # body = json.loads(body) - #except json.decoder.JSONDecodeError as e: - # return body - - return body - else: - return body - - if isinstance(body, dict) or isinstance(body, list): - try: - body = json.dumps(body) - except: - return body - - return body - - def fix_url(self, url): - # Random bugs seen by users - if "hhttp" in url: - url = url.replace("hhttp", "http") - - if "http:/" in url and not "http://" in url: - url = url.replace("http:/", "http://", -1) - if "https:/" in url and not "https://" in url: - url = url.replace("https:/", "https://", -1) - if "http:///" in url: - url = url.replace("http:///", "http://", -1) - if "https:///" in url: - url = url.replace("https:///", "https://", -1) - if not "http://" in url and not "http" in url: - url = f"http://{url}" - - return url - - def return_file(self, requestdata): - filedata = { - "filename": "response.txt", - "data": requestdata, - } - fileret = self.set_files([filedata]) - if len(fileret) == 1: - return {"success": True, "file_id": fileret[0]} - - return fileret - - def GET(self, url, headers="", username="", password="", verify=True, http_proxy="", https_proxy="", timeout=5, to_file=False): - url = self.fix_url(url) - - parsed_headers = self.splitheaders(headers) - parsed_headers["User-Agent"] = "Shuffle Automation" - verify = self.checkverify(verify) - proxies = None - if http_proxy: - proxies["http"] = http_proxy - if https_proxy: - proxies["https"] = https_proxy - - auth=None - if username or password: - auth = requests.auth.HTTPBasicAuth(username, password) - - if not timeout: - timeout = 5 - if timeout: - timeout = int(timeout) - - if to_file == "true": - to_file = True - else: - to_file = False - - request = requests.get(url, headers=parsed_headers, auth=auth, verify=verify, proxies=proxies, timeout=timeout) - if not to_file: - return request.text - - return self.return_file(request.text) - - def POST(self, url, headers="", body="", username="", password="", verify=True, http_proxy="", https_proxy="", timeout=5, to_file=False): - url = self.fix_url(url) - - parsed_headers = self.splitheaders(headers) - parsed_headers["User-Agent"] = "Shuffle Automation" - verify = self.checkverify(verify) - body = self.checkbody(body) - proxies = None - if http_proxy: - proxies["http"] = http_proxy - if https_proxy: - proxies["https"] = https_proxy - - auth=None - if username or password: - auth = requests.auth.HTTPBasicAuth(username, password) - - if not timeout: - timeout = 5 - if timeout: - timeout = int(timeout) - - if to_file == "true": - to_file = True - else: - to_file = False - - request = requests.post(url, headers=parsed_headers, auth=auth, data=body, verify=verify, proxies=proxies, timeout=timeout) - if not to_file: - return request.text - - return self.return_file(request.text) - - # UNTESTED BELOW HERE - def PUT(self, url, headers="", body="", username="", password="", verify=True, http_proxy="", https_proxy="", timeout=5, to_file=False): - url = self.fix_url(url) - - parsed_headers = self.splitheaders(headers) - parsed_headers["User-Agent"] = "Shuffle Automation" - verify = self.checkverify(verify) - body = self.checkbody(body) - proxies = None - if http_proxy: - proxies["http"] = http_proxy - if https_proxy: - proxies["https"] = https_proxy - - - auth=None - if username or password: - auth = requests.auth.HTTPBasicAuth(username, password) - - if not timeout: - timeout = 5 - if timeout: - timeout = int(timeout) - - if to_file == "true": - to_file = True - else: - to_file = False - - request = requests.put(url, headers=parsed_headers, auth=auth, data=body, verify=verify, proxies=proxies, timeout=timeout) - if not to_file: - return request.text - - return self.return_file(request.text) - - def PATCH(self, url, headers="", body="", username="", password="", verify=True, http_proxy="", https_proxy="", timeout=5, to_file=False): - url = self.fix_url(url) - - parsed_headers = self.splitheaders(headers) - parsed_headers["User-Agent"] = "Shuffle Automation" - verify = self.checkverify(verify) - body = self.checkbody(body) - proxies = None - if http_proxy: - proxies["http"] = http_proxy - if https_proxy: - proxies["https"] = https_proxy - - auth=None - if username or password: - auth = requests.auth.HTTPBasicAuth(username, password) - - if not timeout: - timeout = 5 - if timeout: - timeout = int(timeout) - - if to_file == "true": - to_file = True - else: - to_file = False - - request = requests.patch(url, headers=parsed_headers, data=body, auth=auth, verify=verify, proxies=proxies, timeout=timeout) - if not to_file: - return request.text - - return self.return_file(request.text) - - def DELETE(self, url, headers="", body="", username="", password="", verify=True, http_proxy="", https_proxy="", timeout=5, to_file=False): - url = self.fix_url(url) - - parsed_headers = self.splitheaders(headers) - parsed_headers["User-Agent"] = "Shuffle Automation" - verify = self.checkverify(verify) - proxies = None - if http_proxy: - proxies["http"] = http_proxy - if https_proxy: - proxies["https"] = https_proxy - - auth=None - if username or password: - auth = requests.auth.HTTPBasicAuth(username, password) - - if not timeout: - timeout = 5 - if timeout: - timeout = int(timeout) - - if to_file == "true": - to_file = True - else: - to_file = False - - request = requests.delete(url, headers=parsed_headers, auth=auth, verify=verify, proxies=proxies, timeout=timeout) - if not to_file: - return request.text - - return self.return_file(request.text) - - def HEAD(self, url, headers="", body="", username="", password="", verify=True, http_proxy="", https_proxy="", timeout=5, to_file=False): - url = self.fix_url(url) - - parsed_headers = self.splitheaders(headers) - parsed_headers["User-Agent"] = "Shuffle Automation" - verify = self.checkverify(verify) - body = self.checkbody(body) - proxies = None - if http_proxy: - proxies["http"] = http_proxy - if https_proxy: - proxies["https"] = https_proxy - - auth=None - if username or password: - auth = requests.auth.HTTPBasicAuth(username, password) - - if not timeout: - timeout = 5 - if timeout: - timeout = int(timeout) - - if to_file == "true": - to_file = True - else: - to_file = False - - request = requests.head(url, headers=parsed_headers, auth=auth, verify=verify, proxies=proxies, timeout=timeout) - if not to_file: - return request.text - - return self.return_file(request.text) - - def OPTIONS(self, url, headers="", body="", username="", password="", verify=True, http_proxy="", https_proxy="", timeout=5, to_file=False): - url = self.fix_url(url) - - parsed_headers = self.splitheaders(headers) - parsed_headers["User-Agent"] = "Shuffle Automation" - verify = self.checkverify(verify) - body = self.checkbody(body) - proxies = None - if http_proxy: - proxies["http"] = http_proxy - if https_proxy: - proxies["https"] = https_proxy - - auth=None - if username or password: - auth = requests.auth.HTTPBasicAuth(username, password) - - if not timeout: - timeout = 5 - - if timeout: - timeout = int(timeout) - - if to_file == "true": - to_file = True - else: - to_file = False - - request = requests.options(url, headers=parsed_headers, auth=auth, verify=verify, proxies=proxies, timeout=timeout) - if not to_file: - return request.text - - return self.return_file(request.text) - - -# Run the actual thing after we've checked params -def run(request): - print("Starting cloud!") - action = request.get_json() - print(action) - print(type(action)) - authorization_key = action.get("authorization") - current_execution_id = action.get("execution_id") - - if action and "name" in action and "app_name" in action: - HTTP.run(action) - return f'Attempting to execute function {action["name"]} in app {action["app_name"]}' - else: - return f'Invalid action' - -if __name__ == "__main__": - HTTP.run() diff --git a/http/1.3.0/Dockerfile b/http/1.3.0/Dockerfile deleted file mode 100644 index 9bbc5110..00000000 --- a/http/1.3.0/Dockerfile +++ /dev/null @@ -1,27 +0,0 @@ -# Base our app image off of the WALKOFF App SDK image -FROM frikky/shuffle:app_sdk as base - -# We're going to stage away all of the bloat from the build tools so lets create a builder stage -FROM base as builder - -# Install all alpine build tools needed for our pip installs -RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev - -# Install all of our pip packages in a single directory that we can copy to our base image later -RUN mkdir /install -WORKDIR /install -COPY requirements.txt /requirements.txt -RUN pip install --prefix="/install" -r /requirements.txt - -# Switch back to our base image and copy in all of our built packages and source code -FROM base -COPY --from=builder /install /usr/local -COPY src /app -RUN apk add curl - -# Install any binary dependencies needed in our final image -# RUN apk --no-cache add --update my_binary_dependency - -# Finally, lets run our app! -WORKDIR /app -CMD python app.py --log-level DEBUG diff --git a/http/1.3.0/api.yaml b/http/1.3.0/api.yaml deleted file mode 100644 index 1fee36c4..00000000 --- a/http/1.3.0/api.yaml +++ /dev/null @@ -1,522 +0,0 @@ -walkoff_version: 1.3.0 -app_version: 1.3.0 -name: http -description: HTTP app -tags: - - Testing - - HTTP -categories: - - Other - - HTTP -contact_info: - name: "@frikkylikeme" - url: https://github.com/frikky - email: "frikky@shuffler.io" -actions: - - name: GET - description: Runs a GET request towards the specified endpoint - parameters: - - name: url - description: The URL to get - multiline: false - example: "https://example.com" - required: true - schema: - type: string - - name: headers - description: Headers to use - multiline: true - required: false - example: "Content-Type: application/json" - schema: - type: string - - name: username - description: The username to use - multiline: false - required: false - example: "Username" - schema: - type: string - - name: password - description: The password to use - multiline: false - required: false - example: "*****" - schema: - type: string - - name: verify - description: Check certificate - multiline: false - options: - - false - - true - required: false - example: "false" - schema: - type: bool - - name: http_proxy - description: Add a HTTP proxy - multiline: false - required: false - example: "http://192.168.0.1:8080" - schema: - type: bool - - name: https_proxy - description: Add a HTTPS proxy - multiline: false - required: false - example: "http://192.168.0.1:8080" - schema: - type: bool - - name: timeout - description: Add a timeout for the request, in seconds - multiline: false - required: false - example: "10" - schema: - type: bool - - name: to_file - description: Makes the response into a file, and returns it as an ID - multiline: false - required: false - options: - - false - - true - example: "true" - schema: - type: bool - returns: - schema: - type: string - example: "404 NOT FOUND" - - name: POST - description: Runs a POST request towards the specified endpoint - parameters: - - name: url - description: The URL to post to - multiline: false - example: "https://example.com" - required: true - schema: - type: string - - name: body - description: The body to use - multiline: true - example: "{\n\t'json': 'blob'\n}" - required: false - schema: - type: string - - name: headers - description: Headers to use - multiline: true - required: false - example: "Content-Type: application/json" - schema: - type: string - - name: username - description: The username to use - multiline: false - required: false - example: "Username" - schema: - type: string - - name: password - description: The password to use - multiline: false - required: false - example: "*****" - schema: - type: string - - name: verify - description: Whether to check the certificate or not - multiline: false - required: false - options: - - false - - true - example: "false" - schema: - type: bool - - name: http_proxy - description: Add a HTTP proxy - multiline: false - required: false - example: "http://192.168.0.1:8080" - schema: - type: bool - - name: https_proxy - description: Add a HTTPS proxy - multiline: false - required: false - example: "http://192.168.0.1:8080" - schema: - type: bool - - name: timeout - description: Add a timeout for the request, in seconds - multiline: false - required: false - example: "10" - schema: - type: bool - returns: - schema: - type: string - example: "404 NOT FOUND" - - name: PATCH - description: Runs a PATCH request towards the specified endpoint - parameters: - - name: url - description: The URL to post to - multiline: false - example: "https://example.com" - required: true - schema: - type: string - - name: body - description: The body to use - multiline: true - example: "{\n\t'json': 'blob'\n}" - required: false - schema: - type: string - - name: headers - description: Headers to use - multiline: true - required: false - example: "Content-Type: application/json" - schema: - type: string - - name: username - description: The username to use - multiline: false - required: false - example: "Username" - schema: - type: string - - name: password - description: The password to use - multiline: false - required: false - example: "*****" - schema: - type: string - - name: verify - description: Whether to check the certificate or not - multiline: false - required: false - options: - - false - - true - example: "false" - schema: - type: bool - - name: http_proxy - description: Add a HTTP proxy - multiline: false - required: false - example: "http://192.168.0.1:8080" - schema: - type: bool - - name: https_proxy - description: Add a HTTPS proxy - multiline: false - required: false - example: "http://192.168.0.1:8080" - schema: - type: bool - - name: timeout - description: Add a timeout for the request, in seconds - multiline: false - required: false - example: "10" - schema: - type: bool - returns: - schema: - type: string - example: "404 NOT FOUND" - - name: PUT - description: Runs a PUT request towards the specified endpoint - parameters: - - name: url - description: The URL to PUT to - multiline: false - example: "https://example.com" - required: true - schema: - type: string - - name: body - description: The body to use - multiline: true - example: "{\n\t'json': 'blob'\n}" - required: false - schema: - type: string - - name: headers - description: Headers to use - multiline: true - required: false - example: "Content-Type: application/json" - schema: - type: string - - name: username - description: The username to use - multiline: false - required: false - example: "Username" - schema: - type: string - - name: password - description: The password to use - multiline: false - required: false - example: "*****" - schema: - type: string - - name: verify - description: Whether to check the certificate or not - multiline: false - required: false - options: - - false - - true - example: "false" - schema: - type: bool - - name: http_proxy - description: Add a HTTP proxy - multiline: false - required: false - example: "http://192.168.0.1:8080" - schema: - type: bool - - name: https_proxy - description: Add a HTTPS proxy - multiline: false - required: false - example: "http://192.168.0.1:8080" - schema: - type: bool - - name: timeout - description: Add a timeout for the request, in seconds - multiline: false - required: false - example: "10" - schema: - type: bool - returns: - schema: - type: string - example: "404 NOT FOUND" - - name: DELETE - description: Runs a DELETE request towards the specified endpoint - parameters: - - name: url - description: The URL to post to - multiline: false - example: "https://example.com" - required: true - schema: - type: string - - name: headers - description: Headers to use - multiline: true - required: false - example: "Content-Type: application/json" - schema: - type: string - - name: username - description: The username to use - multiline: false - required: false - example: "Username" - schema: - type: string - - name: password - description: The password to use - multiline: false - required: false - example: "*****" - schema: - type: string - - name: verify - description: Whether to check the certificate or not - multiline: false - required: false - options: - - true - - false - example: "false" - schema: - type: bool - - name: http_proxy - description: Add a HTTP proxy - multiline: false - required: false - example: "http://192.168.0.1:8080" - schema: - type: bool - - name: https_proxy - description: Add a HTTPS proxy - multiline: false - required: false - example: "http://192.168.0.1:8080" - schema: - type: bool - - name: timeout - description: Add a timeout for the request, in seconds - multiline: false - required: false - example: "10" - schema: - type: bool - returns: - schema: - type: string - example: "404 NOT FOUND" - - name: HEAD - description: Runs a HEAD request towards the specified endpoint - parameters: - - name: url - description: The URL to HEAD to - multiline: false - example: "https://example.com" - required: true - schema: - type: string - - name: headers - description: Headers to use - multiline: true - required: false - example: "Content-Type: application/json" - schema: - type: string - - name: username - description: The username to use - multiline: false - required: false - example: "Username" - schema: - type: string - - name: password - description: The password to use - multiline: false - required: false - example: "*****" - schema: - type: string - - name: verify - description: Whether to check the certificate or not - multiline: false - required: false - options: - - false - - true - example: "false" - schema: - type: bool - - name: http_proxy - description: Add a HTTP proxy - multiline: false - required: false - example: "http://192.168.0.1:8080" - schema: - type: bool - - name: https_proxy - description: Add a HTTPS proxy - multiline: false - required: false - example: "http://192.168.0.1:8080" - schema: - type: bool - - name: timeout - description: Add a timeout for the request, in seconds - multiline: false - required: false - example: "10" - schema: - type: bool - returns: - schema: - type: string - example: "404 NOT FOUND" - - name: OPTIONS - description: Runs a OPTIONS request towards the specified endpoint - parameters: - - name: url - description: The URL to HEAD to - multiline: false - example: "https://example.com" - required: true - schema: - type: string - - name: headers - description: Headers to use - multiline: true - required: false - example: "Content-Type: application/json" - schema: - type: string - - name: username - description: The username to use - multiline: false - required: false - example: "Username" - schema: - type: string - - name: password - description: The password to use - multiline: false - required: false - example: "*****" - schema: - type: string - - name: verify - description: Whether to check the certificate or not - multiline: false - required: false - options: - - false - - true - example: "false" - schema: - type: bool - - name: http_proxy - description: Add a HTTP proxy - multiline: false - required: false - example: "http://192.168.0.1:8080" - schema: - type: bool - - name: https_proxy - description: Add a HTTPS proxy - multiline: false - required: false - example: "http://192.168.0.1:8080" - schema: - type: bool - - name: timeout - description: Add a timeout for the request, in seconds - multiline: false - required: false - example: "10" - schema: - type: bool - returns: - schema: - type: string - example: "404 NOT FOUND" - - name: curl - description: Run a curl command - parameters: - - name: statement - description: The curl command to run - multiline: true - example: "curl https://example.com" - required: true - schema: - type: string - returns: - schema: - type: string -large_image: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABjCAIAAADihTK7AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4wgeDyYG/VPJzQAAMI9JREFUeNrFvXecJWd1Jvyc81bVzd238+SomZFGYZRRQAkLIQQ2XuEFG4zBXsv2EtYG24vZ9WKC1wmEZbDXWnvx77Mx0ZhgsUgIS8gSoJxnNDnHzuHmW/We8/3xVtW9HUYJyXvV6l9P9e0Kz3vyed5zSVXxar4UEFUoDFN6cLIWHphq7Jlq7ploHJxpnaq0p5u2GSmIiAAiqIKgAMBEVAxoMO+t7M2sL2fOGshtHsiu6w0yHqcntKJE1HWFV+VFrx5YqhDVFKNGaJ86WXvkWOXJE9X9083Jum1ZVcAwPMOGDDGINIGYQABIKT6VFY1EFWCmYsDLCv5Zg9mLlxcvXVE4oz/buSKUiV4l0F4VsERVAUMEoBXJQ0fn7t47/dCRypHZVjOCZyjjk2+YiYigCSJdMBFIAQIAIsAJnBIBBFWySqHVllVR7c2Ysway160r/tS60ub+nDuJ1VcFslcYLFEAykQADk83v7l9/M590/smmpEgF3DGYyJSgqp7dIdDDIsCcDoIAjiWMkIXap3Hd0ADsKKNSNtWe7N80XD2Z7f0vWFDT9bjVwOyVwysbqV79lT1S0+N3rV7eqIR5TzOBx4xRcmViIhASgQih0hiq6AOmhgydN7ewXXRAwDOWkWi9UisYEt/5q1nlt+6pbcv6znIzCuE2CsDlpUYpl1jtdsfPHHX3ql6KKWs5xtWkDg0KX5aItIEgdg2EUgJ7jhUGQATlsTotKLiECaiRiiNSNb0eO/c2veOs/uLgVGFAj+5+f9JwUrvY7oe/tWPj3356dFKW3uzHjOJOrGgLv1agBQTOWlKMCCOn7tLoLoRSnUx1c4lUWNCM0KtbTf1Zd574cBbNpW7V/T/DVjp5b+9Y/zWfzt6cLrRk/M9Jok1i4mgMUBIkXLCpGBnygECE6AAxWChI4NIQHaKuxC45P7Tg+njOMjqoTQjuX5t8XcuGT6jP+t++bKV8uWD5ZAarbQ+8f0Dd+ycygcm4xtRxPeeyFS39oFjJ6cgZUrMF8U/USJOFCMao+FQ7gKx+6f07tN/0nzICDrbsqWA33/BwHvOHQRgFeZl4fVywFKFQpnoB3snf//O/UcrYV/eV4UARKwEAhGTxFqTPn4aOhEI7GJIYgUiRaQQ9xUjEQeZhskw+cweEzOYSGKfm7wvuSl3do3h7eClqoYQis617A1rix9/7fKhvPfyrP5LBks0jgz+8odHPvNvRz3PFAIORTWVDRDxaWWKmcAsSm1rm1YtOGO4mOH+rF/OmnLW9GaMb4gJqmhYnWvrXNtON2W2bRsWqvAMsoYCJiKSVJYAOKFGilH6DarqtHKqEa3tCf70muUXjuRehny9NLAcUs1QPvx/93z9mbH+fEDMoqkrAkBMpESaKJIzWERkmKxSPZKWIO/z2t7MucO5bSO5zf2ZVaWgP+flfbO0IEPnWnaiER2ei3ZOt3ZMtHZPt8cbVhU5nzOGANjuh0geSd1/Gh9TVY9Qi4SBP7hi+D9sKr/UQOwlgOVEd6rW/rV/2vngkdnBQsaqxv6JEgUkQuL+EiEjNtSKtBpJzjfnDudft67nqjWlLQOZwHD3+Z1j7ZKO2PUtfpqZln1movnA8fqDJ5tHqxETih4zkdVEvFLEEsDUnV+VCVZ0thn91oX9771wWDRZ5VcQLIfUqUrrV768Y/totT8fhAJiIpAkuCww6kRkDDWt1tq6vCd40+byz27pO284l/p8q85rIg1FE1FKbq7riCYwUFfEVAujB0+17jhQfXi02bIoBcxQ2yVNi7+nGfpU3d6yrfy7lwzHJuSVAstp36m55i998dndE61y3kQCYtbEeoOBGDpSQJl8plBoNrTreoN3njf0c1v7BvOBe2yRxL4tupDqPNREl44k40pGkn4C2D7Z/Ore6j3H6i2rPT5LIkpQ0i6gEjFTVTAw3ghvObfvw5cOW1HmF8brhcFySE1UW+/8x2d2jTXLeS9SAMTMnWiTQMQOOmYippmmlHPeL1849O5tQ2WXdiyqoijm4aXqIikKRV3c5TPFochpFEUBURCUCQDtnG79n+fm7jteD5hyHkUSw+8UcB5UzqED4/Xwvdv6Pnjx8Ivxjy8AlvtlrR394j8+88SxSrkQRKJEHGscJXEmwMRCMIbaVhuhvumsvv96xfK15ZyDafG6LXBGqiDSk9XwIz8an2qK71EzwuvX5H7rgr5I4PFC67bgdBKXvgDoD443/vLZ2f1zYV9gREXVPaSqxqghsfeqysBEI/qdiwZ/bVv/C+L1fGA5lWGm3/inHd/ZMT5YyIRxhJCE2pyEUkRExIZmmzJY8H7/mpVvOWsQQCRqFtkDpyBMaIRRYIxxcYZqI5Rfvvvk9ql2T2AE8AxNt+yHLijfcnZfqo+VdlQKGCCrtNjxpzWPWiife3bm6wdqWUMBwUonnlDtIOUwBDDTtJ++ZtmbNpSeHy/G6V8iapg+fe+Bf3l2bLAQhKLJcyfpXleua5gm6tHlq4vffMeWt5w1aEVV4S3yzJKEPN/YM/uXT0w6PXJY3Pbk1Pap1kjB8z3K+RwYWlbwbn+u8sREkykODmbb8j8enTlSDQ3NDxcQS5bziQWff+/C/j9+zUDO44Yl33CcZnW+iNLAkKiUMR/98diOiaYhktNr2mnBctnMd3eMfvb+IwOFTGS1k5NR/Gdx7stERBP18D9dNPyF/7hpVU/W/S0ttfJMaFn70R+e+uC9J96wocdjsqqG8NR445/2VgfzfqRJkQ8gIp/4tqfnIhGCWtVVxaDo89vvmbzneM0QRLH40QxBAat4/er83147uK7Xn4mQMUxJQkFMzA6l+C59pkjxu/efmmlGlBifFwuWq0wdnar/t+/syQdenEXESJEmkqUEJgJophl99HWrP/66tQSyunRyb0WZMFaPfvm7R29/euqnN/WcN5ST2HLp3zw7173miFMZKmZ4+0x497EGU5zGvHtzMefzf3+88n/2zLlAf/GjEWAIkei6UvA3Vw9cNpKZDtX3WGOQQEScQAYiAUo+H5gL//ChMSIIlkZrCbA0Niv6ke/snmpI4MUxOiUVy1QHXS2q0rZ/duPaWy5ebgVEZFz0sBT6JyqtX7zj8JNjraG895YzepAUM58cazx0qtGTNdpVb0h8COU8/trBhlsDUVqW965clgk8/vzuxm3bl8bL/dNjEkUp4D+/vP+nVmWn2up7zEnQ4taGOXZWFhjIeXccrP/znhlDZJeSriXAElFD9IVHj9+7d7o371lRV5sDdQe7scbPtsJP3bju7ecOR6KGQbEvdz9Il/bpWK39njuPHqmEeZ9WFv3XriwATjDxz/tqAuLUlDCBSZlgGMSFwOyZix4fbxHU2anXLw+s6mDOfPlg83PPzTHpfEOjlKDHBFHyDP/RpeXrV2Vn2mo8BjMTJ8+gnYVR9AT8mSemTtZCXsp4LQRLFEx0fKZ56w8OlXK+lfjiScUtKTARmDDVjD5x/dqfO2coEvWYUl2baYYnKi0m1kT/m5H+57uPH5wNB/L+XFuvWl3IeRyJGtKJRvTjk41iYCS5CBETc7LmygwQ3Xm8hSR2v2gwuzznNSIdytIX9te/dKBmCKksiGLfXJiE6+C4r8afvKTvNcNBJYTPpAwX8hEhkS0CkPFouim3Pjq+pEdcLFlKhD+958BErR0Y7gosKF0yBTxDE/XofZctf/eFyzpIqRqmwzONzz4yWvCNC7KtKhP+4IHRx0cbAzkTivqGr1uVR+zp6f7j9cmWBKZT3lJOFxvMrKCCx49PhtMtcU6w4PMFA34jUlEMZMxf7aw9NNZwjkwBJqq05fZdVaJOIZdAPtEfXdq7vsfUBcbEFj7ReCYiYraKvizfeah2/9EqL3K488ByFuSRwzPffna0nHfxZ0eU4moU4BuabkQ3bi5/5NrVabHUihqiE5XWe7598KYzyr1ZDyBReExf3zX1lZ0zwwUvUrQFK4retuEsANck/bcTDc+QWyRwnHsTEXO85gB8xkTTPjHZdjcJ4LIhPyktIGf4T56pTTQjp31W9YLBbCW0n3hqlkld6kMEAXoD/48uKWc8cpF10iNSmleGJc/w556cCq3yfOM7DywCiepnfnDQ2Z1umUp/MEyNUFf2BH/2xg2apMGiMEwT9dbPfHnPpSuLF68oujiQCccr7T96cLycNVZhiFoW5wzmSoFnRZloshHtmArzHgsSe8sduVpQhn9oPEzv+Nw+vzegSKGKDNN4Uz77XJVIVeMm2ge2lu4/1f7M9jlDlCaSVrGhx//wtlLNqknCh0SgYxkToBjwM5OtO/bPEqHbcs3rgDPh/n1TP9w/3ZP1rKZNhKSnkCDWsvonN64fyAfi2nsKIjRC+yvfOjjXsh++ckVyx0qQP35wdKYlgRc/sFW9eFkWSQXqmcnWVNMGJtWEuK8VO/UuS5o1tH06bFprGKoYzpn1RdOyyoRItTfgfz3ReuBUy1koUWQNf+Cswt/srv/jvmoawRqCVXrDqsLPrM3OhuozJ9dNFSi29Hmf//65mWYkTB3h4q6lA4DPP3TMVQ+65SppV8FnmqpH77pg+Or1fZFIEiUoQf/r9w//4FDl1y8e7s95AriO9ANHa9/dX+nPsZVYALOe2TaUSS/3xHhbnAYS4lycujqoqR0FAqaTDXugEhEQKQBs7fPbogxyxiFj+H/vqbesdXm1Am9YlbtwwL/tueo9JxqpB2CCqH7g7NLKgtfWTsstNl2JJuY93j0dfu9wlZIKRwcsZ62eODr3wP7pUsZbKsogAjUjWV3OfOiqVaJqiBFnefTZB09+bcfUmUPZt2/t18S+ta3e9ug4E6xAQQpuWhopeBt7MwA8IlU8Pdn0DIlCQOJa+UktQVS7vxTaiPTpqSjueIDOLnsKWFVRiUQyBntnom8dbjjdEaWs4beszTLRnz5b3T3TdAi6P+4NvPdtLTasMif2mByVoJPFBYa+umvWmYsYAufvXB3mQ9/Y8ZUnR/vzvtU0LkRaXPeNmaiHt/70hreft6y7B6eqeyfrRJT1aHVvNtXX0MqBmaZvTAq2KDIGK0u+y5dE9Uilra4QNs88Lt15jlSLHoZznis6tKycqEdMHeUQqE+6Mu/H/W1F20anmhopSgZDOS/VbRf6feDH04+NtwoMayVNrUVURFWVoHNN+7evX3H5yoKrkXipdIxVW/fumSwGpttfprUQJqq27fkrim89Z1jULQgUcUK/ebCQ6kvnbpi2DOQXPbVzTjEC63qC+QXRBf2tJV6apPMZw+tL/uI3RAKTUAAynllbjIk4kSZVkuR1y5bC4+MtpCQnpPro5AgCfGt/5fKVBUrV0Onk93ZOjFbagfPnRPMrRsRMrcj++muWe8yakjoAj8nriFjnWZlwmlpHxx7xwrZyt9s97df8sy7xBm9e7SwJxIk8ptRau8j+vIHMlcuCSgTmpN/bRT8RaN7nH5+oTzQi5zc8JDnHnc+NGsOdllznOzGh1rZbRwpv2NyvqoZdTovJeutfdk8HxtRDe+WanrOH80lFCfcdnts3HWY918WIWxoC/PSGUjljREEkPz7ZOFiRrElbQS9QCidCy8rVyzIr854rYDw12dw5K1nTSU2Y0LJ69Yi/ouAn1lPvPt6YbZNC+wO6fmVGlBKODt6+Mf/AqXbcJHehB5TiHygwNFaP7j9Wu3lTr6h6zlodnW48daySD4ykUt71YqJGaH9+20jgmUjUIwjUEP3VQ6duffDkYCEzF9o7fmGL0zHDVA+jj9x36uBsO+cbMJhdf4yzvrl+TbGcccvAt++oPDzaKmWMMhkmYmJ6vtaUIUy15JMX0VvXepFqQLRjJvqfT1f7M5yaDvee956Zf/9WX+JwgR4ZD798sNEXcCi6PN+7tS/jqh2quHAgc16/v30yzDGJKClApBozVAAYxn1Hqjdv6iWieInu3zc1VQv7i8ESpS9C28pI0X/z1gF3bVEYopOV1jd2Tm3oz4ZKW4Zzl64spqL42KnGWD1a05MRKDGY2TC3BOt7g+G8k2U0rdRCXZE3gWc0zgXx/JLlEYj4VM2mgr+l1x/OcdHjjncn5Ix5dCKMVD0iqwD0hpWZ759o9QU82ZKvHGx+si+jiaIZohtXZZ8Yb+cMYtPl3Jrze6I5j58eb041ov6c5yw1Htg/yadhmBhCtW1fu753qBCIanqirz07OdmIPKZqy24bzhmmdHnvO1yJRK1qpLAK93PLSjnLaXw43bTTLStEoaoFWcULfoUKVZysW6cpAPoz7BFFop33CDzCkZrdOxumq72lN+jLcDXSgkcPjrUPVyMmiKOmANcszw5mjZU05kwqTEQgBIZG6/aZsQZcL6YR2mdPVDIeL1FIczZYceOWgbR5x0zNyN6xazKfNJ2cWKmqqwQ9fKKe9bibkcBEVml5wUfiTyaaUo/UcNq6eVHtS0MYa4kibnb0Z7jow85LzpzY6uOTbXdOBXoD3lDyWqIeUy2Su4833XEiiNJA1jt/0G9EGufy82NMAqzqY6MNOG+4f7x2Yq4VeOw4AQtebavDpcxla3pdaO2K6I8cq+ybauR9DkULPp89nEtV4+hs+/Bs20HfHQiI6kAujmMBjDeipKj/YvvnqvCIZtvSjOL7LBgqeLwghBbAI3pmOnRxgluzs3pNJFAga8wDo+22tdy1RJcNZ6Jk1bohICJRBIa2TzQBZQA7T1VrbWs6jJTuVaJmaM9eVujP+67T7W7s7r0zri4aWowU/dW9QXrh7RONSls86nTS3d8QMJLz0jNPNqVbIl4k48AQqpFWI3Ho+4b6A47md8ZUETAdqthGZFPTsqnXdwYka3CoanfORg5Hp3rnD/gln0N3n2n5PHmkgPlIJZpqCAPYNVYFll5gIrStXrK6hER9PKa2tQ8fq+R8VtW21TW9mbzvpSZ2+1hzyYI/EfqynWh7vGFftEglKMSUPp1rO7AAUMFfWNJUwGdMtuRY3aYH1+ZN3pBbnlD00YkQiSaq6uqit7poml3Flg5jCfAY083o0FyLAeyfqHu8dANRFb6h85cXkeQrAPZPNo/OtB1nPxLZUM4gKbEC2Dfd9Bgp0zg9jyHqDUx6eLZtKb2dhBX0gi8iRIqGRbLq6M8Y1YWwM6ER6ZFqB6yhnCkHsQvymZ6dCpHUXQUwxJt6/VDU0TYpSSHdrbvYbf9Mm0MrJ+daPtNirIgQifZmvfX9OQCcJJLPjtbrbWuS/GutC5wAJkRij1Xaydk69GSB+ky9WZNCONsWg2QjxcJQ/vleVnW6JSlYRS8m+C4Ww6NdklX0MZDhUFSBDNORmp0LJUGEAGzq8RPB6lrjJN8T1UOzbZ6qh5PVtjG8VIBFodWhoj/sUrDkJLvGawCc+TJMy3s6CdpUQybrkWc6OyXi1VEwIWsIcaFDq22hJFJ24fKLcYcEWEU16rw3Y5aGmQgnap2OCUBD2Th29RjTbTlWs531BNYUjXFv1aTXN19Uj861earWTsRkibAhsjJSCgJjnBN0Vu/gZMs35FgXHmMw3zHbs62oFjqRmd9vATyGT52Va1lNLWgM54uLHgAKu2Qp6/HigEcBA5puW9c2cOLTn2GJ928gtHq0GqErd1+eNzmPbPcaa0okARON1kJvotpqhuI7T79gjQiR6HAxQEIGYoKqTtRD38RbRnIel7MdyZppSiSa8TCvxgMACJgCr6NvLuFIm9uuBveCzGsmYpJWV2Gkxyd2dMMuyAjwGbOhuuzNodWfcXzN+C3jTZvKIICeDGc9brQtI+1JJaBBDWGqEXmVprWq/iLBUlUCqaI/5ydHQIS5lj0622pGYhWRatY3eb/j4ybq4XQjIiLRpCXHMIYiVcPkKI1EENXplm1EEjcqDAwxJQ3j5wHLkNZCTLc7xsgj1CPJmHmdGOfyJhsSKYKk0pA1XAslwwCoHumRmu0+c8HjvEe1VpwzdquGe/BmpJ7bmkWgpdrgEGg556ELyUjkzKFcqDBEFsj5JushlZeCT5esKPRkfUnWzBELrKKc9fx4SwBFIht6/FJgAhM3OZnZsRBoKYPQLVmVtgy4JwYA9GXovAG/5HfSw+QSKAcQEbDn3jmSM+f1e72BUUU1ksEsxRzFGErKGRJHfUrEJf1OQEvgNSMR1a6qPLS78KAIvJTIDwD9ee8rv3Bmd/FeVYB4O+FVa4pXr+05jWtTEdfZIyb69GuH5v/2hct+3acC4DMBuGwwuOyq4PRX1DQ3um559rrlma5fqiNUJU9HHjkLRTH3rZvKC1hRz4r79YKrJAzIRXaE4NKLpK4IdBd2QcbOh36eXHBcYjacZo4L6H8vKoDornsp8ekZZtRNglOgO5ij+b9F3KmiBc4p7msrROElFCr3Jk1aQukFVBbey2K6V1edGM9PnovfqZp2AV5iFL/UFZ/HzHVryeJ3akpMAACIpMFhzAnv2nkAA3jZuAo7LzBKVEIBNMPOHxBhuh7+7ncPNEU9ZlFkPPrUjevKucC1MB46Vrn1odFS2nZMWKai6M2aP75qJO97gIZiP/7w5HhDMj7HjVpOm4ZEnFJ2Fr6YUAn1plXZn1mTjwQe49Hx1uf31EvBwqTHKnp8/r3zinmPHU3w/lOtLx6o9/gMoNKW1wx6v7ylKBrTXK3YtpVY/1S7NdEJl2Hysh6ZOIjuaBYSagMRpuohumh+nuEnTtTG6mHGM04ya6GWc/Efz7bk3kNz/fnAxqyDuHtqgb6817bI+1CFz2bHZHvPdFjIeM5+ctJcdR3p07lFjzHZ0gsHAsCxqGi8qQ+Ot/szHHUFX0RoC5bnnG2Pjcyxmn1orN0fsKhONmVNngHWREqbFvVIieJtBt3kcHfIN/BKOc909TY60gs4fuZ0I+wGsZTxVpcztdDmA6NAqKh2OfJlxWAo72d9J1NMMd2blCggNG1cYGJCX4YLPhc9t5VHCcScyBeDCbQUXoaoLdKf6RibULTk84JCDQMNwVCWHd/ZiWnTasnnvEcq2jS6pphs6FCAUAu11raMVPvm2XirWvCYh4rZTMJ4SZFC0un0mE7Ndbg+TtSHC34rih1NI5SZZgesnoANwYqKqIVYVVcpFdGmjYPJVNMjkUjUioioqFpRtWJFrWgksCJWVNINUNrpvGa6crNqpFYXvQ0IRXsDJuLUqE+2rBWJBJGSKEaSEog711TL1kPhlPqdksABKKzoYM7jgYLveoXxhslu1KAe02il1YyieOuEKoCNA9lIxAXfVmS81k7/pC9negITiSQUCU1PGFqEVhILSlkX1MQWIbb67gjENTtJZGHa5HD2u9xYwyo6S5C8AxCBE8DEF+hkU6CqIlYkQ7qmFKCrunCyFjUiS12s7/hHcRZNhwqGyzl/sBhEVpYwqQqfeaIanpxto+t+tg7nKeaWQxTHO9Vu9GS8wZyJbLJkXWFwZLURdR6rEBgrXbY0lXnXG046xCIqIt3VJUPalbmjZZcuWljVlflY0VzXb6wuBqSq7Uj6M7y6GLco3OvgbLKUibXSuHbk4gZd25tlw7SynA07ZOR5ymgYlVa0d6IGx8omAnDuskJPhq2qQgk4ONOIF1OViVb3ZiKrHDMDnOooAaGVqUaUSBbKPrs5DQkq8XFRURGkTXQnaqKSeDuPqDdwETwBmG27KGShABqmNYXONrNKaMebkSFAtB3K+pKX9zhldALYNdVmQDpt/G4tVCZsLAcMYNNQwS7RAovNfCT61PEqEsOs0DXl3Ib+bDMUQH2mQ9POqMXOe/NAJnJaGPNr4lgwEp3rcgXljHFapvMkqeOtncqISGJBRESsiGHkvHjLOYCZtvAiqKwiy1hb7IB1qm5nmtaoQKVt9YKBuA7uHqptZfd0y2dIZ0tBZ4eBFc15vKGcYQBbhgvzttSkNw2IamDosSNzSHYVicAwXbmutxkKgMDQsZnWXCtMBfPsobwhgSROuStlGK/bVA2H8txtGZzGqVKCF1RS4yWqKgIVRFazhLIfN51UdS4UpgXioK1IBwJake+AdbASNUJh1chq1uhly7KxwXK139nW0UroM0kXVCqxuISiA1le6+R567JiMestIVyqqprzzXNj1eOzTRdgOFBu2NSf8UgFPmOs2j443UzNxtnD+XLWi0RIiURIHeaqirF6lL5tKOcZitPFLrwk2SEizvFBFFZgRUVFNbQoeCj6TrKoZWWyaSnR01hbFc1INpS8TBcbYdd0KKJQrUeyudffVPZVNaU+PHqqWWtbRrwwqQLGZjGSDeWgJ2MYwLqB/JpythXJ/B5JvMI+01Qt/NGBKSRWSRXnryicPZyvta3HVA/lmZP1VKqXl4KNfZlmJLF3TUBn0rFalNrUobzxSK0LGrr304g6qxWT8RO5UhWIhNb2ehQk5IZqqNWWkMKKM20xxmEk2/rjYglDFdgx1fJIATQj+alVeUMxjq5se/+xmkfosledaIugkZVtwzkAbEV9wxes6mmFktJP5gUQCsN0567J9DlFlYlvPmewFVmPKTD0xImqMyIiSsDlq4qR1cDAEBkmw2xYMgYuyHDqPJTzenwDVY/UIxgiQzDkqoDKIIYy1BAMlAlG1YdCdFmOU27MdFvqoXgQUiEVhjAUagtGLx4K3P0TYaoZHp4LCx5FIv0ZvnFtPjWyRDhSCZ8aa+ZMytKCSkdWVOEzLl6eBxBXhK8+Y+BLj59c0saLaDEwDx2ePTzdWNuXk4Th+h/OGfrLHx0/PN1g5u/vnZ5phOWc78Lo69aXPvWjk6MaAiQER0BuCZ6baDYjcYNi+rKGCccrYd7nlLff2TeQsq45JtggyXX6M3EHAaBDc+HxSrscdIghRKiHcv5gsKEnkzKufniisXuqPZg14/XwP53dO5z3HTlNREF018HKTNMOZDlciJQC2rJ2Rck7d6QAIE6jr9zQP1LKVNtRd6vVpewK9ZgnauE/Pz36oWvXqSozqaI369/2M2fsGm9kfRNajWJ6MgE4d6T42ZvWNSNh7sxjcBIqsTNGYOi/Xz40Wo8Cw9rZ7ULxNKPknxwnqXEHoGn1spGsJtuD1pa8j13SlzWUxG9EhGYkW/p8V/BkQKGriv4nLxvIGW5Gcv2anEIZ5HLXtrV37K/kDKyoyjykVNUjnQvl0g29pcBY532cJbrly898Z8dYOeenlj4lVxvmtuhwMbjrNy4sBjHZUBcFgvpyCy6v6muJ3gKAZHfo9w7Ovf+eE+UMR1YXyBRUGJhp2r++ad0bN/Za0Q754M1nDy0uw2vCOM16vH+y/vWnR4nIxYcuCg1jUwzMRyqysjhQ7Nw+gC4W8Et69vl/tbgnFKeK6SVSO2xd+pm8j6Ci8v9tn/YIIpJGxSlSBLQiXd3jv3Z1EQAzdZh/124aXNOXHa+FvlnYcE0sl/f5h46/7fyRnO/H2wWIPOhopTnTFCYMFvxyLjarzBirtmea1uOkVUUkqqWARgquCqyhlaOVkJcS0yW7rgoEjJXF+BKRyNFKe37dFJGV1T1+JmX9qhyrRqGgbWV9bxAkx90Ohu8fqjx6stYbeNZ25VvxvANlUDW0b1vT53TQEHkAiGBFe3P+TVuH/9cPjwwU/KgrF3P0LlHN+7x/ovF3Dx//wFXr3OYTVRBoumHf9sXnphr2TWf2/++bN4ubskJ8YKb5jq/tK2Y8Rz4zRLOhXLGq+OWbNzovUQ/1Hd8+VAnV43SAhibKn/4PB4fHmGnJWzeV/ueVI6Goz3RwNvqF7570TUITBeqRntmX+fsbl2uifUer4TvuPDFalxvW5P/iumWOuedWJrTy2ccnfeLu1MbJFikUJNC8obee1Zcu3LxmxDsvWdmT9aLu6LRTV9VItDdnbv/xsaPTDcc9YoJVPXO4+LHXr4Xq/YdmnxurUryVRy5b1XP9ht5KKwqYDIgIBY8OTLdmm9Zxyfpy3qa+TDsSj8BKDJASK1jBUAJIlRQEdbVxUZw/lE1vaudUsxZZQ3AdZENoW/mVc0ouFnV567f2VY7ORetL3scvH0qIe/Feki/smHp2tJH3yAUMiVipSwcYqDbt5SuL5y0rpDSOGCz38GcMFW44c7DajBZsRU0tl0c024g+cfe+tFzoMVvRm88Zef8VK45MN77wxJiLRd1mhd+8fLnPJCpQUdWAMVkLd082AbgluXBZLrRCgPND8UQBcalivGEAKlCJIskbPW8wSJf2qfFmnHGrEnSmaS8eyV6/pigqDDAw24q+vmeuaPBnVw0PJpQpV6Q7Mtf83GOTpYCjBCmoUNyAFoWQClTfvW0QSXkdWLSF7pYrVgfePJvVCVBVrWhfzv+/z03+01MnDFO8m4nJqn74urW/dMGyv3v05MGphvOWVvTskcI7zxuYqoUeQwUEall54mQtPflrVhQ8UtG4+t0pz1iIRXf1oRHZVUVvQzlQt0VV5OmxZsCudiiu3PihC/qZGHADluiLz00/N9H89LXD54/kbZLcuIX46P2js+3I49hIpaV3UgHUU6q15KLl+avX9jj+z0KwnHCdv6r3jVuHZhtht3B11wOsaE/G/MFdB/ZP1BxeFBNs8Bdv2XTZ6t7fv/uAS1BdiPxbV6xY35ept8WQqqpP9MjxKgA3h2bbcG5FwWu51nRXGbczrEIgFqSot+WikZzPJrJKwJFKeHCmlWGNrBhgvB6+a2vP+SN5qwDIEMbq4W2PTd567fI3biiHIu6BrajHfPuTY/ccqpYDE8V2XeapoYCgkchvXDximLr3Sy+ULAU+eN2GYmBkUV4dK6OIx1Rv2/d/Y2cjjLqyawoMf+WdW5uhfGfnmEnC0d6s/4fXr2mGLsOTnEfbR+uT9bYhikRLGe+i5flGS0y8bWfplyNdX7OqkNhgPHKyPtMIScHQ2Va4tT/4zYuGEvutAD7xo9H/ctHAu8/pj0RdZdWKekw/Ola59ZGJ/ixHViievuLyGuvkyzDm2vbqNaXXre8Vnbf1YR5YTrg2DxffdcnK6UbozReujnsWLWW8p49Xf/tbu5jIqmiy2yrnm394+9bdY43RSst1wiKRq9eVP3jF8rFqaIh8xmi1/cjxmiYV/Rs29Eq8vMkImaTy4JAioBnJipJ3yfIckjLWfUeqpICgHalH+NQ1I67IBYUheuDI3FUrc++/cCgScYetwjAdmG5+4O5jQbytJM1uJBYoBZFaUQ/021esWEwkWChZDBLFf7luwxkD+UZol6xzQTWyMpD3v/Hs+Mfv2usxu+aFK0gUAvOBK1dyUityHuA3r1jx1q39Y9W2zwTVf90/TUlX/eo1pVUlvxWKwwsST0FB4hYYWm3ba1fnS4EXiXqMiXr7sZONgsdWdLYlf3rNirMGC5Go21Qiqmf0Z96+td+KeOzGi6ghTNTDX/3u4UoryrALT0VVRSUpf6sCHnimbn9xW/95ywq20wk+DViOY1nOeR994xnNtl3cjErjkUh0qBD89YPH/+zeA4bJipseCVUEnjdUzKTBIhOJ0q03bbhufc/JSrucNT88XJlpRoZjTbxxY0+1FXkxP05UJSmQgCCiEjBu3tyLpNlx39HqWDUMDMYb4SdeO3zTxl4rSPWAiZYXM6JkOB7taohmmtF7vnPo0Eyz6HNo47IkqZCCNOZwsaLesmf0B7952cqUJf98YAFwZvvGrSM/f9HyqVp7aWVUdc5xKO//+X1H/uRfD3jMzpHRIla7Cy0zhv/2Zze9bl1puh6OVcN79s8kD4+fP3ug4LOosCqE4tKUiqoYRbVpX7M8d/6yQjo09jt754h0sh598rUjv3TuYCgyf2xZZyqpG4YzXgvf9e1DO0YbvYFpWxs/h8QwxfVzBSBtG33s2lU9GQPo4oxy6YkhzpF97KYtm4by9ZbtNnILTK8VHSz4tz1w5MPf2e2asjbJHBcIrChyvvn8zZveds7ATCP86rPjgLqF2TyYe+PG3pmG9ZgIQukeTVUFQpFbzh8ikFUwYe9U896Dc3mD265f8Z5tg6n9nne5xLZ6TPummm/7xr7tY7VylkIrLCBRdlEVACUIoOKRTtXCWy4euXp9uXtD5guD5dgPvTn/L37uHGKyqksm7ileQwX/Hx49+e4v7Ziotd3zL06RXT/KN+bTb9z4mTeuf/BI5e690+xuFfS+1ywreBzZJG6AQsUnzDai160tXbOuKG4wLvRTD55YVfS+evP6t2zpj7q0r/slCqvqMd13aPZt/7zv2GxYznAUKatSQpzUxA+qikeYaURXrin9zpUrreppd+Z87GMfOw1eZEVXlrNDxeBfnh3NB95SdGZKyLwoZcyusdpdOye2jpTW9GUJagUL1ocSob9wRena9T1PnqhcuKLkMQt0IO8T6V17pss5AwUre0yhikf0v960vi/nuXLYwenGeC383I3rVpYy6TyJBQroKCpM+NzDJ//bvcdVNe9TJB22UcKYivuCjgc+nPf+7ubNvdlOZWEJTHQJIei83A194s49n73/0HApG9kO+yLZ7xmXNN3ux0Ykovq+K9d84OpVgfGsKmGJmfbJmCqxQsn8LAD49W/v+/auqVLWqBAIbaufvWntzVsH0wS4FUZZ38NpJtyl06/2TdU/dt/xfztc6c8ZgETUjW9wkeL8DraKwFr9wn/cdMmqnuefn/VCk9ncvDHC+776zNefHhsqBWEXdzMdspNKmVuT6UZ0/srSR65ff9WGPgBWhIgWLJdoOscU6b032+En7zt6vGIDQy0rb95cftu5wwtmIi85z9Cquo3zrUg+/+TY7Y+OVUPbmzGukUEKxLP/OnUBJBvJqy37uTevf9OZAy84fPmFZ/6530civ/qlp+/cOTFUPA1endkI8A1X21ZU33z20HuvXL11pIg4Clw8haB7YZxPmmdGRZVPv9SutejFG7b1jt1Tf/3I2PaxRjnrttbFGHWvfUJWgwEINNOM/viGNe/YNrKkUr9ksJDIfCO0v/blZ7+3a8JxI55HvpBQTGcatpDx3rx14BcvXn7BypJzU1YVp0FNk9Ft6T8XK0VimzU9QzOy39s78/dPTTx+spIxXPA56sjQQmqJK9e7uQSzjfDj1699z0XLXgxSLxasdIXbkfznrz5zx/bxwWLQXfZagJej3JCbeiuYa0bZwFy+vuet5w5fs7G/r4srHrvNtIFDS9T10VUXoPn71PdNNr67Z/qO3VO7JxqB4WLAoouq1d17z+A2RSK02gjtJ29Y987zR1786POXMAHX4WVFfvubO7/42ImBQqDz1y1tcKS23/3omjq1tg0Vq8qZy9eWr91YvnBVcU05h9O0OJ6n99GKot0TjQePVO4/NPfUyfpsyxZ8zvnkmFmLtj2kZ4yTTcPUCIWgn75pw5vPGnxJQ+Jf2mzlmDIP/Pm9Bz997/6cz75nurtBne8JeJocMUTE1LJSD0WBvlywcTB3zkjhzJH8+v7c8p6gL+sVMp6bDZUsjzRCqbXtRD06PtvaN9l8bqz+3Hj96GyrEWpgqBAYz4V1mlKt5rWHNaUzAAA8pplGtLIn+PM3b7x0dc9LHaf/kqd2azLf/I7to7/37Z2zzagn67+gSnYfMUwghKKtSNsWCvUNFTKmGPjFDOd8N7WbItW21VrbVtu21pZGJFbhEWU8zph4WN7z9Ic6TYSYHQtSTNWjq9b33PqmM1b0ZF7GBw+8zOH57kp7x6q/882dDx6aKef9tHC6GC93qPNz8hY3f8+FqlZhgcgJg6omzQpmRwCIybwJySQpLWv3brKuWTPz9/EYRq0tIvKrly7/3WvWeMz/TvPgF+AVWrnt3gO3//hoM5LerJe0lGI8Ohz0rhEkCyBLh9clY4c7aowF7MEln6774KJHceM5ZxrR5sHc/7h+7bUb+3UeC//fCyx0BUFPHZv9w7v2PnBgOh94OZ9TyGi+QKXh2Hz2ftq6T1mLHasHINnIoKcDbMlfOC79XCvKGLzzomW/9drVbnrTT/JJPD/xp6Mkg3JV9UuPHf+rBw7vn2gUMibrLQ1Zt1ZinpQhDRziEmUsdV3TGYF0F5IjK4ibWJzeSWf/jNbaVkWv2VD+4NWrt62Ix3z+hB/A88p87k4qYjP18B8ePvqPj584PNXI+SYXGGjM7scCEXNskYV4JZh1JAzoAot0Xp1JOxIXj3SzqtWWZeCS1aVbLlv5+s0DOM0k7P9nYLlX6l+mau2vPnnya0+c2DVaY6J8YDw351ahiyDrpm0soSLzLJiz6NS944aST8pqRVJv20LAV6wrv+ui5a/b1I8u3/2KPOAr/Flh2vUhWI3Qfn/n+DefOfXw4dnJWtszlPM938RD9TpWuws7nX9kwa2m6Dnj5iZUtK00QmHCmr7sdRv7bj5v+PyVPfHivXIffPWqgLUYMgCHphr/umv8nj0TzxyvTNbaAmQ8znjGfUxM6vU699RBrYNnqopWJBRtW4msBoZX92UvWdN7w5bBK9eXSxkTXx2vMEyvIlgxZMknMKSh37HpxpNHZx85PP3syerRmeZMI2xZAOCYTZmQ/5AaecQU3IQtZIjyAQ8W/I2D+W0rS5es7jl3RU8pE/MXrSgRXiml+3cFK325ds2CKfon51qHJmr7Jur7J+pHZ5pjc63persRaWjVuqiTySPOGipkeLiYGenJrOnPbxzMbRoqrClnS9nOlv9kJupPEBS8uNf/DxrLmMA1V9rHAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE5LTA4LTMwVDE1OjM4OjA2LTA0OjAwvtdu7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxOS0wOC0zMFQxNTozODowNi0wNDowMM+K1lIAAAAASUVORK5CYII= diff --git a/http/1.3.0/requirements.txt b/http/1.3.0/requirements.txt deleted file mode 100644 index d91f4447..00000000 --- a/http/1.3.0/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -uncurl==0.0.10 -shuffle_sdk diff --git a/http/1.3.0/src/app.py b/http/1.3.0/src/app.py deleted file mode 100755 index 0dfb56ca..00000000 --- a/http/1.3.0/src/app.py +++ /dev/null @@ -1,456 +0,0 @@ -import time -import json -import ast -import random -import socket -import uncurl -import asyncio -import requests -import subprocess - -from shuffle_sdk import AppBase - -class HTTP(AppBase): - __version__ = "1.3.0" - app_name = "http" - - def __init__(self, redis, logger, console_logger=None): - print("INIT") - """ - Each app should have this __init__ to set up Redis and logging. - :param redis: - :param logger: - :param console_logger: - """ - super().__init__(redis, logger, console_logger) - - # This is dangerously fun :) - # Do we care about arbitrary code execution here? - # Probably not huh - def curl(self, statement): - process = subprocess.Popen(statement, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, shell=True) - stdout = process.communicate() - item = "" - if len(stdout[0]) > 0: - print("Succesfully ran bash!") - item = stdout[0] - else: - print("FAILED to run bash!") - item = stdout[1] - - try: - ret = item.decode("utf-8") - return ret - except: - return item - - return item - #try: - # if not statement.startswith("curl "): - # statement = "curl %s" % statement - - # data = uncurl.parse(statement) - # request = eval(data) - # if isinstance(request, requests.models.Response): - # return request.text - # else: - # return "Unable to parse the curl parameter. Remember to start with curl " - #except: - # return "An error occurred during curl parsing" - - def splitheaders(self, headers): - parsed_headers = {} - if headers: - split_headers = headers.split("\n") - self.logger.info(split_headers) - for header in split_headers: - if ":" in header: - splititem = ":" - elif "=" in header: - splititem = "=" - else: - self.logger.info("Skipping header %s as its invalid" % header) - continue - - splitheader = header.split(splititem) - if len(splitheader) >= 2: - parsed_headers[splitheader[0].strip()] = splititem.join(splitheader[1:]).strip() - else: - self.logger.info("Skipping header %s with split %s cus only one item" % (header, splititem)) - continue - - return parsed_headers - - def checkverify(self, verify): - if str(verify).lower().strip() == "false": - return False - elif verify == None: - return False - elif verify: - return True - elif not verify: - return False - else: - return True - - def checkbody(self, body): - # Indicates json - if isinstance(body, str): - if body.strip().startswith("{"): - body = json.dumps(ast.literal_eval(body)) - - - # Not sure if loading is necessary - # Seemed to work with plain string into data=body too, and not parsed json=body - #try: - # body = json.loads(body) - #except json.decoder.JSONDecodeError as e: - # return body - - return body - else: - return body - - if isinstance(body, dict) or isinstance(body, list): - try: - body = json.dumps(body) - except: - return body - - return body - - def fix_url(self, url): - # Random bugs seen by users - if "hhttp" in url: - url = url.replace("hhttp", "http") - - if "http:/" in url and not "http://" in url: - url = url.replace("http:/", "http://", -1) - if "https:/" in url and not "https://" in url: - url = url.replace("https:/", "https://", -1) - if "http:///" in url: - url = url.replace("http:///", "http://", -1) - if "https:///" in url: - url = url.replace("https:///", "https://", -1) - if not "http://" in url and not "http" in url: - url = f"http://{url}" - - return url - - def return_file(self, requestdata): - filedata = { - "filename": "response.txt", - "data": requestdata, - } - fileret = self.set_files([filedata]) - if len(fileret) == 1: - return {"success": True, "file_id": fileret[0]} - - return fileret - - def prepare_response(self, request): - try: - parsedheaders = {} - for key, value in request.headers.items(): - parsedheaders[key] = value - - cookies = {} - if request.cookies: - for key, value in request.cookies.items(): - cookies[key] = value - - - jsondata = request.text - try: - jsondata = json.loads(jsondata) - except: - pass - - return json.dumps({ - "success": True, - "status": request.status_code, - "url": request.url, - "headers": parsedheaders, - "body": jsondata, - "cookies":cookies, - }) - except Exception as e: - print(f"[WARNING] Failed in request: {e}") - return request.text - - def GET(self, url, headers="", username="", password="", verify=True, http_proxy="", https_proxy="", timeout=5, to_file=False): - url = self.fix_url(url) - - parsed_headers = self.splitheaders(headers) - parsed_headers["User-Agent"] = "Shuffle Automation" - verify = self.checkverify(verify) - proxies = None - if http_proxy: - proxies["http"] = http_proxy - if https_proxy: - proxies["https"] = https_proxy - - auth=None - if username or password: - # Shouldn't be used if authorization headers exist - if "Authorization" in parsed_headers: - #print("Found authorization - skipping username & pw") - pass - else: - auth = requests.auth.HTTPBasicAuth(username, password) - - if not timeout: - timeout = 5 - if timeout: - timeout = int(timeout) - - if to_file == "true": - to_file = True - else: - to_file = False - - request = requests.get(url, headers=parsed_headers, auth=auth, verify=verify, proxies=proxies, timeout=timeout) - if not to_file: - return self.prepare_response(request) - - return self.return_file(request.text) - - def POST(self, url, headers="", body="", username="", password="", verify=True, http_proxy="", https_proxy="", timeout=5, to_file=False): - url = self.fix_url(url) - - parsed_headers = self.splitheaders(headers) - parsed_headers["User-Agent"] = "Shuffle Automation" - verify = self.checkverify(verify) - body = self.checkbody(body) - proxies = None - if http_proxy: - proxies["http"] = http_proxy - if https_proxy: - proxies["https"] = https_proxy - - auth=None - if username or password: - # Shouldn't be used if authorization headers exist - if "Authorization" in parsed_headers: - #print("Found authorization - skipping username & pw") - pass - else: - auth = requests.auth.HTTPBasicAuth(username, password) - - if not timeout: - timeout = 5 - if timeout: - timeout = int(timeout) - - if to_file == "true": - to_file = True - else: - to_file = False - - request = requests.post(url, headers=parsed_headers, auth=auth, data=body, verify=verify, proxies=proxies, timeout=timeout) - if not to_file: - return self.prepare_response(request) - - return self.return_file(request.text) - - def PUT(self, url, headers="", body="", username="", password="", verify=True, http_proxy="", https_proxy="", timeout=5, to_file=False): - url = self.fix_url(url) - - parsed_headers = self.splitheaders(headers) - parsed_headers["User-Agent"] = "Shuffle Automation" - verify = self.checkverify(verify) - body = self.checkbody(body) - proxies = None - if http_proxy: - proxies["http"] = http_proxy - if https_proxy: - proxies["https"] = https_proxy - - - auth=None - if username or password: - # Shouldn't be used if authorization headers exist - if "Authorization" in parsed_headers: - #print("Found authorization - skipping username & pw") - pass - else: - auth = requests.auth.HTTPBasicAuth(username, password) - - if not timeout: - timeout = 5 - if timeout: - timeout = int(timeout) - - if to_file == "true": - to_file = True - else: - to_file = False - - request = requests.put(url, headers=parsed_headers, auth=auth, data=body, verify=verify, proxies=proxies, timeout=timeout) - if not to_file: - return self.prepare_response(request) - - return self.return_file(request.text) - - def PATCH(self, url, headers="", body="", username="", password="", verify=True, http_proxy="", https_proxy="", timeout=5, to_file=False): - url = self.fix_url(url) - - parsed_headers = self.splitheaders(headers) - parsed_headers["User-Agent"] = "Shuffle Automation" - verify = self.checkverify(verify) - body = self.checkbody(body) - proxies = None - if http_proxy: - proxies["http"] = http_proxy - if https_proxy: - proxies["https"] = https_proxy - - auth=None - if username or password: - # Shouldn't be used if authorization headers exist - if "Authorization" in parsed_headers: - #print("Found authorization - skipping username & pw") - pass - else: - auth = requests.auth.HTTPBasicAuth(username, password) - - if to_file == "true": - to_file = True - else: - to_file = False - - request = requests.patch(url, headers=parsed_headers, data=body, auth=auth, verify=verify, proxies=proxies, timeout=timeout) - if not to_file: - return self.prepare_response(request) - - return self.return_file(request.text) - - def DELETE(self, url, headers="", body="", username="", password="", verify=True, http_proxy="", https_proxy="", timeout=5, to_file=False): - url = self.fix_url(url) - - parsed_headers = self.splitheaders(headers) - parsed_headers["User-Agent"] = "Shuffle Automation" - verify = self.checkverify(verify) - proxies = None - if http_proxy: - proxies["http"] = http_proxy - if https_proxy: - proxies["https"] = https_proxy - - auth=None - if username or password: - # Shouldn't be used if authorization headers exist - if "Authorization" in parsed_headers: - #print("Found authorization - skipping username & pw") - pass - else: - auth = requests.auth.HTTPBasicAuth(username, password) - - if not timeout: - timeout = 5 - if timeout: - timeout = int(timeout) - - if to_file == "true": - to_file = True - else: - to_file = False - - request = requests.delete(url, headers=parsed_headers, auth=auth, verify=verify, proxies=proxies, timeout=timeout) - if not to_file: - return self.prepare_response(request) - - return self.return_file(request.text) - - def HEAD(self, url, headers="", body="", username="", password="", verify=True, http_proxy="", https_proxy="", timeout=5, to_file=False): - url = self.fix_url(url) - - parsed_headers = self.splitheaders(headers) - parsed_headers["User-Agent"] = "Shuffle Automation" - verify = self.checkverify(verify) - body = self.checkbody(body) - proxies = None - if http_proxy: - proxies["http"] = http_proxy - if https_proxy: - proxies["https"] = https_proxy - - auth=None - if username or password: - # Shouldn't be used if authorization headers exist - if "Authorization" in parsed_headers: - #print("Found authorization - skipping username & pw") - pass - else: - auth = requests.auth.HTTPBasicAuth(username, password) - - if not timeout: - timeout = 5 - if timeout: - timeout = int(timeout) - - if to_file == "true": - to_file = True - else: - to_file = False - - request = requests.head(url, headers=parsed_headers, auth=auth, verify=verify, proxies=proxies, timeout=timeout) - if not to_file: - return self.prepare_response(request) - - return self.return_file(request.text) - - def OPTIONS(self, url, headers="", body="", username="", password="", verify=True, http_proxy="", https_proxy="", timeout=5, to_file=False): - url = self.fix_url(url) - - parsed_headers = self.splitheaders(headers) - parsed_headers["User-Agent"] = "Shuffle Automation" - verify = self.checkverify(verify) - body = self.checkbody(body) - proxies = None - if http_proxy: - proxies["http"] = http_proxy - if https_proxy: - proxies["https"] = https_proxy - - auth=None - if username or password: - # Shouldn't be used if authorization headers exist - if "Authorization" in parsed_headers: - #print("Found authorization - skipping username & pw") - pass - else: - auth = requests.auth.HTTPBasicAuth(username, password) - - if not timeout: - timeout = 5 - - if timeout: - timeout = int(timeout) - - if to_file == "true": - to_file = True - else: - to_file = False - - request = requests.options(url, headers=parsed_headers, auth=auth, verify=verify, proxies=proxies, timeout=timeout) - if not to_file: - return self.prepare_response(request) - - return self.return_file(request.text) - - -# Run the actual thing after we've checked params -def run(request): - action = request.get_json() - authorization_key = action.get("authorization") - current_execution_id = action.get("execution_id") - - if action and "name" in action and "app_name" in action: - HTTP.run(action) - return f'Attempting to execute function {action["name"]} in app {action["app_name"]}' - else: - return f'Invalid action' - -if __name__ == "__main__": - HTTP.run() diff --git a/shuffle-tools/1.0.0/Dockerfile b/shuffle-tools/1.0.0/Dockerfile deleted file mode 100644 index 5c1a8af4..00000000 --- a/shuffle-tools/1.0.0/Dockerfile +++ /dev/null @@ -1,27 +0,0 @@ -# Base our app image off of the WALKOFF App SDK image -FROM frikky/shuffle:app_sdk as base - -# We're going to stage away all of the bloat from the build tools so lets create a builder stage -FROM base as builder - -# Install all alpine build tools needed for our pip installs -RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev git - -# Install all of our pip packages in a single directory that we can copy to our base image later -RUN mkdir /install -WORKDIR /install -COPY requirements.txt /requirements.txt -RUN pip install --no-cache-dir --prefix="/install" -r /requirements.txt - -# Switch back to our base image and copy in all of our built packages and source code -FROM base -COPY --from=builder /install /usr/local -COPY src /app - -# Install any binary dependencies needed in our final image -# RUN apk --no-cache add --update my_binary_dependency -RUN apk --no-cache add jq git curl - -# Finally, lets run our app! -WORKDIR /app -CMD ["python", "app.py", "--log-level", "DEBUG"] diff --git a/shuffle-tools/1.0.0/api.yaml b/shuffle-tools/1.0.0/api.yaml deleted file mode 100644 index 43fabb22..00000000 --- a/shuffle-tools/1.0.0/api.yaml +++ /dev/null @@ -1,814 +0,0 @@ ---- -app_version: 1.0.0 -name: Shuffle Tools -description: A tool app for Shuffle -tags: - - Testing - - Shuffle -categories: - - Testing - - Shuffle -contact_info: - name: "@frikkylikeme" - url: https://shuffler.io - email: frikky@shuffler.io -actions: - - name: repeat_back_to_me - description: Repeats the call parameter - parameters: - - name: call - description: The message to repeat - required: true - multiline: true - example: "REPEATING: Hello world" - schema: - type: string - returns: - schema: - type: string - - name: router - description: Reroutes information between different nodes - returns: - schema: - type: string - - name: get_cache_value - description: Get a value saved to your organization in Shuffle - parameters: - - name: key - description: The key to get - required: true - multiline: false - example: "timestamp" - schema: - type: string - returns: - schema: - type: string - - name: set_cache_value - description: Set a value to be saved to your organization in Shuffle. - parameters: - - name: key - description: The key to set the value for - required: true - multiline: false - example: "timestamp" - schema: - type: string - - name: value - description: The value to set - required: true - multiline: true - example: "1621959545" - schema: - type: string - returns: - schema: - type: string - - name: send_sms_shuffle - description: Send an SMS from Shuffle - parameters: - - name: apikey - description: Your https://shuffler.io organization apikey - multiline: false - example: "https://shuffler.io apikey" - required: true - schema: - type: string - - name: phone_numbers - description: The receivers of the SMS - multiline: false - example: "+4741323535,+8151023022" - required: true - schema: - type: string - - name: body - description: The SMS to add to the numbers - multiline: true - example: "This is an alert from Shuffle :)" - required: true - schema: - type: string - returns: - schema: - type: string - - name: send_email_shuffle - description: Send an email from Shuffle - parameters: - - name: apikey - description: Your https://shuffler.io organization apikey - multiline: false - example: "https://shuffler.io apikey" - required: true - schema: - type: string - - name: recipients - description: The recipients of the email - multiline: false - example: "test@example.com,frikky@shuffler.io" - required: true - schema: - type: string - - name: subject - description: The subject to use - multiline: false - example: "SOS this is an alert :o" - required: true - schema: - type: string - - name: body - description: The body to add to the email - multiline: true - example: "This is an email alert from Shuffler.io :)" - required: true - schema: - type: string - returns: - schema: - type: string - - name: filter_list - description: Takes a list and filters based on your data - skip_multicheck: true - parameters: - - name: input_list - description: The list to check - required: true - multiline: false - example: '[{"data": "1.2.3.4"}, {"data": "1.2.3.5"}]' - schema: - type: string - - name: field - description: The field to check - required: false - multiline: false - example: "data" - schema: - type: string - - name: check - description: Type of check - required: true - example: "equals" - options: - - equals - - 'larger than' - - 'less than' - - is empty - - contains - - contains any of - - starts with - - ends with - - field is unique - - files by extension - schema: - type: string - - name: value - description: The value to check with - required: false - multiline: false - example: "1.2.3.4" - schema: - type: string - - name: opposite - description: Whether to add or to NOT add - required: true - options: - - False - - True - multiline: false - example: "false" - schema: - type: string - returns: - schema: - type: string - #- name: multi_list_filter - # description: Takes a list and filters based on your data - # skip_multicheck: true - # parameters: - # - name: input_list - # description: The list to check - # required: true - # multiline: false - # example: '[{"data": "1.2.3.4"}, {"data": "1.2.3.5"}]' - # schema: - # type: string - # - name: field - # description: The field to check - # required: true - # multiline: false - # example: "data" - # schema: - # type: string - # - name: check - # description: Type of check - # required: true - # example: "equals,equals" - # schema: - # type: string - # - name: value - # description: The value to check with - # required: true - # multiline: false - # example: "1.2.3.4" - # schema: - # type: string - # returns: - # schema: - # type: string - - name: parse_ioc - description: Parse IOC's based on https://github.com/fhightower/ioc-finder - parameters: - - name: input_string - description: The string to check - required: true - multiline: true - example: "123ijq192.168.3.6kljqwiejs8 https://shuffler.io" - schema: - type: string - - name: input_type - description: The string to check - required: false - multiline: false - example: "md5s" - schema: - type: string - returns: - schema: - type: string - - name: parse_file_ioc - description: Parse IOC's based on https://github.com/fhightower/ioc-finder - parameters: - - name: file_ids - description: The shuffle file to check - required: true - multiline: false - schema: - type: string - - name: input_type - description: The string to check - required: false - multiline: false - example: "md5s" - schema: - type: string - returns: - schema: - type: string - - name: translate_value - description: Takes a list of values and translates it in your input data - parameters: - - name: input_data - description: The input data to use - required: true - multiline: true - example: Hello this is an md5 - schema: - type: string - - name: translate_from - description: The source items to look for - required: true - multiline: false - example: sha256,md5,sha1 - schema: - type: string - - name: translate_to - description: The destination data to change to - required: true - multiline: true - example: hash - schema: - type: string - - name: else_value - description: The value to set if it DOESNT match. Default to nothing. - required: false - multiline: false - example: - schema: - type: string - returns: - schema: - type: string - - name: map_value - description: Takes a mapping dictionary and translates the input data - parameters: - - name: input_data - description: The input data to use - required: true - multiline: true - example: $exec.field1 - schema: - type: string - - name: mapping - description: The mapping dictionary - required: true - multiline: true - example: | - { - "Low": 1, - "Medium": 2, - "High": 3, - } - schema: - type: string - returns: - schema: - type: string - - name: regex_replace - description: Replace all instances matching a regular expression - parameters: - - name: input_data - description: The input data to use - required: true - multiline: true - example: $exec.http_headers - schema: - type: string - - name: regex - description: Your regular expression - multiline: false - example: "(Content-\\w+): (.*)" - required: true - schema: - type: string - - name: replace_string - description: Replacement string (capture groups with \1 \2) - multiline: true - example: "Content header '\\1' = '\\2'" - required: false - schema: - type: string - - name: ignore_case - description: "Make regex case insensitive (Default: False)" - multiline: false - example: "False" - required: false - schema: - type: string - returns: - schema: - type: string - - name: parse_list - description: Parses a list and returns it as a json object - parameters: - - name: items - description: List of items - required: true - multiline: true - example: shuffler.io,test.com,test.no - schema: - type: string - - name: splitter - description: The splitter to use - required: false - multiline: false - example: "," - schema: - type: string - returns: - schema: - type: string - - name: execute_bash - description: Runs bash with the data inputted available (TBD) - parameters: - - name: code - description: The code to run - required: true - multiline: true - example: echo "Hello" - schema: - type: string - - name: shuffle_input - description: Alternative data to add - required: false - multiline: true - example: '{"data": "Hello world"}' - schema: - type: string - - name: get_file_value - description: This function is made for reading file(s), printing their data - parameters: - - name: filedata - description: The files - required: true - multiline: true - example: "REPEATING: Hello world" - schema: - type: file - returns: - schema: - type: string - - name: download_remote_file - description: Downloads a file from a URL - parameters: - - name: url - description: - required: true - multiline: false - example: "https://secure.eicar.org/eicar.com.txt" - schema: - type: string - returns: - schema: - type: string - - name: get_file_meta - description: Gets the file meta - parameters: - - name: file_id - description: - required: true - multiline: false - example: "" - schema: - type: string - returns: - schema: - type: string - - name: delete_file - description: Deletes a file based on ID - parameters: - - name: file_id - description: - required: true - multiline: false - example: "Some data to put in the file" - schema: - type: string - returns: - schema: - type: string - - name: extract_archive - description: Extract compressed files, return file ids - parameters: - - name: file_ids - description: - required: true - multiline: false - schema: - type: string - - name: fileformat - description: - required: true - multiline: false - options: - - zip - - rar - - 7zip - - tar - - tar.gz - schema: - type: string - - name: password - description: - required: false - multiline: false - schema: - type: string - returns: - schema: - type: string - - name: inflate_archive - description: Compress files in archive, return archive's file id - parameters: - - name: file_ids - description: - required: true - multiline: true - schema: - type: string - - name: fileformat - description: - required: true - multiline: false - options: - - zip - - 7zip - schema: - type: string - - name: name - description: - required: false - multiline: false - schema: - type: string - - name: password - description: - required: false - multiline: false - schema: - type: string - returns: - schema: - type: string - - name: xml_json_convertor - description: Converts xml to json and vice versa - parameters: - - name: convertto - required: true - multiline: false - options: - - json - - xml - schema: - type: string - - name: data - description: - required: true - multiline: false - example: 'xml data / json data' - schema: - type: string - returns: - schema: - type: string - - name: date_to_epoch - description: Converts a date field with a given format to an epoch time - parameters: - - name: input_data - description: The input data to use - required: true - multiline: true - example: 2010-11-04T04:15:22.123Z - schema: - type: dict - - name: date_field - description: The field containing the date to parse - required: true - multiline: false - example: currentDateTime - schema: - type: string - - name: date_format - # yamllint disable-line rule:line-length - description: The datetime format of the field to parse (strftime format). - required: true - multiline: false - example: '%Y-%m-%dT%H:%M:%s.%f%Z' - schema: - type: string - returns: - schema: - type: dict - - name: compare_relative_date - # yamllint disable-line rule:line-length - description: Compares an input date to a relative date and returns a True/False result - parameters: - - name: input_data - description: The input data to use - required: true - multiline: true - example: 2010-11-04T04:15:22.123Z - schema: - type: string - - name: date_format - description: The format of the input date field (strftime format) - required: true - multiline: false - example: '%Y-%m-%dT%H:%M:%S.%f%Z' - options: - - '%Y-%m-%dT%H:%M%z' - - '%Y-%m-%dT%H:%M:%SZ' - - '%Y-%m-%dT%H:%M:%S%Z' - - '%Y-%m-%dT%H:%M:%S%z' - - '%Y-%m-%dT%H:%M:%S.%f%z' - - '%Y-%m-%d' - - '%H:%M:%S' - - '%s' - schema: - type: string - - name: equality_test - description: How to compare the input date and offset date - required: true - multiline: false - example: '>' - options: - - '>' - - '<' - - '=' - - '!=' - - '>=' - - '<=' - schema: - type: string - - name: offset - description: Numeric offset from current time - required: true - multiline: false - example: 60 - schema: - type: string - - name: units - description: The units of the provided value - required: true - multiline: false - example: 'seconds' - options: - - seconds - - minutes - - hours - - days - schema: - type: string - - name: direction - description: Whether the comparison should be in the past or future - required: true - multiline: false - example: 'ago' - options: - - ago - - ahead - schema: - type: string - returns: - schema: - type: strings - - name: add_list_to_list - description: Adds items of second list (list_two) to the first one (list_one) - parameters: - - name: list_one - description: The first list - multiline: true - example: "{'key': 'value'}" - required: true - schema: - type: string - - name: list_two - description: The second list to use - multiline: true - required: true - example: "{'key2': 'value2'}" - schema: - type: string - - name: merge_lists - description: Merges two lists of same type AND length. - parameters: - - name: list_one - description: The first list - multiline: true - example: "{'key': 'value'}" - required: true - schema: - type: string - - name: list_two - description: The second list to use - multiline: true - required: true - example: "{'key2': 'value2'}" - schema: - type: string - - name: set_field - description: If items in list 2 are strings, but first is JSON, sets the values to the specified key. Defaults to key "new_shuffle_key" - required: false - example: "json_key" - schema: - type: string - - name: sort_key_list_one - description: Sort by this key before using list one for merging - required: false - example: "json_key" - schema: - type: string - - name: sort_key_list_two - description: Sort by this key before using list two for merging - required: false - example: "json_key" - schema: - type: string - - name: diff_lists - description: Diffs two lists of strings or integers and finds what's missing - parameters: - - name: list_one - description: The first list - multiline: true - example: "{'key': 'value'}" - required: true - schema: - type: string - - name: list_two - description: The second list to use - multiline: true - required: true - example: "{'key2': 'value2'}" - schema: - type: string - - name: delete_json_keys - description: Deletes keys in a json object - parameters: - - name: json_object - description: The object to edit - multiline: true - example: "{'key': 'value', 'key2': 'value2', 'key3': 'value3'}" - required: true - schema: - type: string - - name: keys - description: The key(s) to remove - multiline: true - required: true - example: "key, key3" - schema: - type: string - - name: convert_json_to_tags - description: Creates key:value pairs and - parameters: - - name: json_object - description: The object to make into a key:value pair - multiline: true - example: "{'key': 'value', 'key2': 'value2', 'key3': 'value3'}" - required: true - schema: - type: string - - name: split_value - description: The way to split the values. Defaults to comma. - multiline: false - required: false - example: "," - schema: - type: string - - name: include_key - description: Whether it should include the key or not - options: - - true - - false - schema: - type: string - - name: lowercase - description: Whether it should be lowercase or not - options: - - true - - false - schema: - type: string - - name: run_math_operation - description: Takes a math input and gives you the result - parameters: - - name: operation - description: The operation to perform - required: true - multiline: true - example: "5+10" - schema: - type: string - returns: - schema: - type: string - - name: escape_html - description: Performs HTML escaping on a field - parameters: - - name: input_data - description: The input data to use - required: true - multiline: true - example: $exec.field1 - schema: - type: string - - name: field_name - description: The field to HTML escape - required: true - multiline: true - example: my_unsafe_field - schema: - type: string - returns: - schema: - type: string - - name: base64_conversion - description: Encode or decode a Base64 string - parameters: - - name: string - description: string to process - multiline: true - example: "This is a string to be encoded" - required: true - schema: - type: string - - name: operation - description: Choose to encode or decode the string - example: "encode" - required: true - options: - - encode - - decode - schema: - type: string - - name: cidr_ip_match - description: Check if an IP is contained in a CIDR defined network - parameters: - - name: ip - description: IP to check - multiline: false - example: "1.1.1.1" - required: True - schema: - type: string - - name: networks - description: List of network in CIDR format - multiline: true - required: true - example: "['10.0.0.0/8', '192.168.10.0/24']" - schema: - type: string - returns: - schema: - type: string - -# yamllint disable-line rule:line-length -large_image: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAK4AAACuCAYAAACvDDbuAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QA/wD/AP+gvaeTAAAAB3RJTUUH5AgXDjM6hEZGWwAAD+lJREFUeNrtXb/vJTcRH7/v3iVBCqRBiCAQAtHwq4AWRElHwX8AoqbmXwDRpiH/QyQkGoogUSAhKIKUAE1IdSRSREhQQk7c3XtD8X55vePxjNfe3bk3H+nu+96uPf54POtnj8fe8OQX30JwOIxhtzYBh6MGOsPF0z9p2iWwpd8LjX6W5vWUYaiqlBuvLT5b5TQDPlRwmMSAABBg+kCer+XuAeQf4tL9tAxJ/hIfZGSm8rhyEfjytfxr9FeSX+KjvVfipNVpWlaPNhsAEPCS7Ao8FYnRlbO4ksLnjiSQvIanv4FNjwJ5pXIlMq6MQpIqqPnQKQKbjuPDtZlG55o6UHXWtVncZZTbbNBVB1P5dJYguCbJJ1WjOG8PVOioSm5HPrVt1rwuyN+K+PSZnNV1M/MmEFubfFjjU9tmK9XBJ2cOk3DDdZiEG67DJOrGuA7HyvAe12ESAxa73KPrN1z8gUikCCdvcD5NXnpQpA8nNhh9m5Yn4ZMrV8dHV/8a/dRA0x419a3lI9GBtM2GcrGYFXRNUU5TyluTOpdXwqeUt6YOpby9DUTLZylOcRlzdBTf2yV3ZBFOmKSHQh5KpjSSSpqG4s6VkUubqw8W8knTSnWk0Y+2jF5tlmuDUloJn6T8gRVcEpJ+3srChHSNt8RJsq4p+S41LC13KTcu/RJt1pLPKY1Pzhwm4YbrMAk3XIdJTMe4aeCjJhBVk0YiQ1MWZHhLgmO5QNVWfKRlavlIIQnurQmcnaMjSbBxhtMwYUxODpLcl2tUhvPlNE6VkiuoFVLXKT6ZfBjxRIIzOSlgWpLSB8uZ0g3BjeVDlFGEos0mfKKL7CQrY2ES7pM2i/OX22w4/sWReEhEnUOTxx3a+FrawQGZh04/rWe6oJBKo5zT4zLjPHE9ZHym5YzToogzfQcmfLgOhuLF/Sjm2izVDyXnrKtcmmmdaKumf+RyCw5Xn7OmzQaJF0fiEZG6BjXpYUYaSVkaPrXeHe4eVaZEr3Prqrmmrbc2T8lrmOMjn5xJHeJLYkk+PfzNTxOflrwF0EeHbU0Zt2wsW+PTkncB7g5zmMSwzUfS4eDhPa7DJK5jXGorsnZxonbRIbeAoOUjkUvlp+qxFp9YNuWL0nBqsVCkqUsrHQnuX+Nx5/qcJDI0kWgtJh7ihYCN8aG+13DqOXlbWUfD+fN0AUEmp3RcUWlVEwCynb5ssYLnxHViJT6ULCykb8EnzUfpqBWfVAdcnt5tprGhIe10WnjHpB2FtMPWcpM66yXyOad4Lz4Srq34SHhwZfRos1w9Y/jkzGESvj3dYRLe4zpMwg3XYRJuuA6T4M/Hzfk/OGd9OP2HOE2f8wtBlCebJrkfp+Gc3AGmiSiuaVlpwkmajL4osPUm9FMqIzBOJolfjGuzEtdUwWl53Dm7Eh9pzIdps+FiYJyi1N+Rvs/6OLCQBul8Ip8R08ik3EwhLZz1Wv8XmU7ZZqX7OT2gUIB2oaRBm+2ovDm5nM+ulEeiD8yka8UnJ1PCP82r9YWW8iCU5XO8W/PhPmvllNKW7lEyszsgNKuzkspJFZFL15uPtIweq7A1xiKpz1J8tGXP+dE53/fJmcMk6hcgJO8XqokEKi5uYzTG29LqSev95JqyKsoOOxjNpKQBD7VFc5GBJRsi+NQHkkv6+7m/UxTufwLCCy+CbAruyOLDdwEf/uf6vbbNJukzlogZC6wMdhAcM7ohHPawe/GrcO+HPwe4u782G7sIAE9++0vYv/YKwO6usfCaka0etgwXAGB3D8JznwIYnlmbiW0M92FbQy0d+MmZ3Xo5JDDcvuXJ2ZYqtyUuTwuM6nSXctcufHCOZqkjPScXhbIcdeD0XUpfKyNNy8nlyhuozLkM8XxR6pjm7tc4Fdx620I7lWq10JCm0ZanWoBwm3FsBe1WznpadbTg4A9PI2xx7FUKHopQjg7TKqNnpbioIUcFUGUsy1CS8fFYBYdJuOE6TMIN12ESgyiKiwO1bQOJe1w+6p42Etmhwmi6kLZXfC2G9IUj2vulY2wIPrv4onRhIXcRqS0DiWxkhF0uIb37wG22LRCSuVCyekC2GSXj9CG3YyT+krWh+KPAhkTvgGDKqbqnWbBwY+2Pnm3Wy4aMRYc1MuPDvp0skwgAh8PaJGbh5k4kx0f/hce/ewnw/QenXQCTFJDfQy45PzFNn5NHsoPy/u6gzE+nObzz91P9Z+6kWAm2zg6bDMoq8OQxHN78Axze/htAaB1EbQhhdzyfgRqIGoCxoUIjhDuA3ZDpcR0W4C3nMInbNVw7v4oOAsehArVFPL0uOjMM+DlM+pk7t7/BDuwcJsM6gcM7WweOX05nFCHNi12ASRfLo3QaX9O0GWTylOTnZIMwf4YPPTlD4iMm7aZwAGOUf3Rf48wjHNzVOMkKFA8pp0RHZ1mjdihs5R61PWbsWlphgs/E5gptNvFfSLY8QPk7dVbh+UNg8qfnJsZ8Bo0hzF0Y2Nqvc0s+Vbs5YL5OLfPRcorT2hvjtuxyHWZhzHCX6AMcFtB2B0RvtKZqqe6OEYz1uA7HEbdruN7ZmsZtGq4brXnQhlsbLFkDrY9mC9giH41/dSlONfeEIBcgss7nXopInPdkYN95J3XD1bMgkJUNFOxsDNLgyiynhYyX5dnAhnLyhzmO4V7IO8+xyZEgx5UqvJ41rOUTdhBOr2w6KjZc+B1FBkLGVUoAABQEcmPu6rPPw73v/gh2n/wMANYEhAd4/NqvYf/Wn5pEyPW2IUrOzQWSHyHdkEJgN8D97/0Edp/7GgDu9fnDDvD9t+HRqy8BPvxQ9i6xEXUEuPcMDF//Puw+/aVqDewfvA77f/zx9M40e7jNeNw5CDu4++K34e4r36kWcXj3TYDfvwz8D79ml1clDPuxx9FhuUik0rblVihFWLX+7ZFEXE2ioLBNg9fUSRopVsOjJbioskZlDuyAvmflpOWsOUNu/cBQ8jW/1A0np11RG+GjwG36cQHqFWnBcG4Axgx37d/I1uXXcvCnx6BXoQXf3mOAzvVpooJzaOcWdKBH1fZ07dCsFZpNgmfZbaOJ2dxnpwkNFC3C9MBcGxo0OugxwV8LWKm5lg9sFQdszKGhLAla2dCuduuOZcypx+UXdk0OK5e/hXKNTc4cjiPGhtvTX1njI6Z2+vbuKtaKspLooXdkXs1u5yUR7/LdROMsraSSIfTa6pqWodE9Mvla6sCI8d7uUMEXIEzjdg3XYRr2osOePIbDR+9BGO7re78QAD/+AODwpK5sBDg6dGyGAtL1sYnLGDe3+2BNTNycYQf7B2/Aw5d/XB9HejjA4YN3jgHUNQ132MOTv/wG9v98A+CgFBCO/+FH/wJ89PBaSY1OULZzQyQL2skayVwg/7Dk3Ky2IlcEgEcfw/7dt+YJnRP1f9jDoz+/AvM0FU4c1u8mes59e+ZXDhXmPE+tForD+lH73Q6EluiozfaldnzWQUWQzdprPk87lg44nkTKN+DT/10S7lW4VYz8wWucOTAPtl5e4mgfjmu0/b3HdZiEG67DJNxwbxlGhwkAuZeXAJS3Qpfemq7dds1tS5dsbc6dAyQpS5uGe+lKrJLSGUqlCb2GcwUuCxBzt71T2/g7t9mQniofv0yjWOtMYdSLM6Sy0pd5iLdFSQtUyiJtRnjmGOdhqq5bo5WzUXAYzns2Lu2tjaqb0WaTHRBrR9cvEVG4VF3WkLsGnzXqohzjbk3dt4hG/jDDxy8BLL5y5miBZi1wa9vT14dJ0o2qft6/1GhQZ1SV9uJxd3cQ7j+XD7RJ40JK38/XAPKz4ly+OG+KwOTDwn0uDSKEZ58/vgH+hmHLcA97uPvCN+G5H/wMoCaQ/KkAAtzdg/DCZ9cmsipsGS4ce5u7z38DYHhmbTL2YfjBH28DOM80s+MoxllVvfkwKudSbiL0dB0NTya2iGpNYmIzl+/EdexjQ8PEGE4FhdPHMAlbLhcsdWaPnfDEAxQJnbx53TEPJ51j3N7CrEfbSNt+arzXt57X2RBx94LsUGHOGRQtF7Fa8HFQQOabJmc5XQ8b8iAbh0mYNFzvdefD+nRhyPowqWitc2VbRyutGCF18+ilU2mEXWX51zFuKbqlZ/RLy0gixzagiS6sgL2hghuwAywarsMBxgzXO9u2sBzZWHwHRLwrQ5rWYQBIfuwCKnZJEpvEYSg9dRoncnejtdxFbBRLqFQzr5fSudH3nDmOaH26yHIwNcZ1NIZNmwWArYU1Fg8HDLB/7wH879VfAey2Rd0a9g/+2ubUyZUOdAz//umXjT136GPd2cDNnM9bC4Pd1gbOx3WsDh/jOkzCDddhEpcjmKiFhvGLQwDitJNrYTz05H7MS+N56hiq0mbYCfeIj2STb2s+cSJEOrguJ4fScaneOW7kOWZJm4VCmaPFg8wKgcSGuLpzR49Rerm8vIRaaECgvyB1Tbl9qOZoMiykHeVhVoZKwW9N+CSJuPwsH4YY12aTa5TxYyZPpsxSDG/Rhgp1lyxUnK/7UMFhEm64DpNIlnzTAdXcsJml8rdO1yt/K+R45EJUluS9zHaWITuQJb9rsVT+HvuKe+RvhdIIcE3ey4Rj+VDBYRJuuA6TcMN1mMT15SWMZ5h10Oc86+dr50s14QWch7rEh5PHef+psgsyqB0iI2e+hE+pDlpvvkQ/uVUMDfdSnTq12TA58injFUdOMPB5AeiALtHcUrstXrqSINnaoVjxyE5ra1ZipHMsTV2kMiQ8NDw7tdmqQ4WtzNEd9uBjXIdJuOE6TMLoy0sct46KHndNS6d2pW5tp+rW+Jw5rVl2qpP5Oqrcnr52w9RMgbfA8db5tAsp8DGuwyTaGW6DB7ppn9CCzxKnvKz9Kz7j/prUi0cwqQLQDBtvrp5uvMc/Wf00oFAT5FjscbcwMloCt1LPWvTUT41sH+M6TMIN12ESw3UPd8gPtrh7JeTyXvZGn0KD0jSlMms5Sfhw92vkUvXT5tPWt3WbSfjMsSFl3ujlJdy+4xkjnFze+PWrNWXWclqaT6t82vq2bjMJnzk2pMzrQwWHSbjhOkzCDdchxpZchpezwySQvHhiyVMLevPRctXwqeWmfcv5GaVTGKRy557YIHnhpETeoCl05grhbPlL89HK1vCp5darvZbgo+XEwYcKDpNww3WYxC6/U5PY5oun66MzPHH8L05PpqHKghn+TpjyictkZQLPh4u6yeknvXeWU+JD6TDHJ/cbn93Bi8nnDKdJm8EG2+zIZwBudlbjUOYOpj1frClPwyf3OZuXuaEx3lgWZixKxIfZ911rvJO65PRFVmZjbYY+VHDYhBuuwyTccB0mcdkB0cr5z70pW/pm7Bo+LesgqUsrPjVye9WXkqld8FiizRCi6LBWjmTRPGGG/JZ5ejvoa1ai1qwvlWarbeZDBYdJuOE6TKKP4W7xJdFb4+R8ZvH5P852gxhpwOZ9AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIwLTA4LTIzVDE0OjUyOjAwKzAyOjAwetRgVgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyMC0wOC0yM1QxNDo1MTo1OCswMjowMJuxI+oAAAAASUVORK5CYII= diff --git a/shuffle-tools/1.0.0/docker-compose.yml b/shuffle-tools/1.0.0/docker-compose.yml deleted file mode 100644 index 40ee05f6..00000000 --- a/shuffle-tools/1.0.0/docker-compose.yml +++ /dev/null @@ -1,20 +0,0 @@ -version: '3.4' -services: - hello_world: - build: - context: . - dockerfile: Dockerfile -# image: walkoff_registry:5000/walkoff_app_HelloWorld-v1-0 - deploy: - mode: replicated - replicas: 10 - restart_policy: - condition: none - restart: "no" - secrets: - - secret1 -secrets: - secret1: - file: ./secret_data - labels: - foo: bar diff --git a/shuffle-tools/1.0.0/requirements.txt b/shuffle-tools/1.0.0/requirements.txt deleted file mode 100644 index 89ca3320..00000000 --- a/shuffle-tools/1.0.0/requirements.txt +++ /dev/null @@ -1,12 +0,0 @@ -ioc_finder==7.3.0 -py7zr==0.22.0 -rarfile==4.2 -pyminizip==0.2.6 -requests==2.32.4 -xmltodict==0.14.2 -json2xml==5.0.5 -ipaddress==1.0.23 -google.auth==2.37.0 -paramiko==3.5.0 -shuffle-sdk - diff --git a/shuffle-tools/1.0.0/src/app.py b/shuffle-tools/1.0.0/src/app.py deleted file mode 100644 index 9a09e415..00000000 --- a/shuffle-tools/1.0.0/src/app.py +++ /dev/null @@ -1,1305 +0,0 @@ -import asyncio -import datetime -import json -import markupsafe -import os -import re -import subprocess -import tempfile -import zipfile -import base64 -import ipaddress - -import py7zr -import pyminizip -import rarfile -import requests -import tarfile - -import xmltodict -from json2xml import json2xml - -from json2xml.utils import readfromstring - -from ioc_finder import find_iocs -from walkoff_app_sdk.app_base import AppBase - - - - -class Tools(AppBase): - """ - An example of a Walkoff App. - Inherit from the AppBase class to have Redis, logging, and console - logging set up behind the scenes. - - """ - - __version__ = "1.0.0" - app_name = ( - "Shuffle Tools" # this needs to match "name" in api.yaml for WALKOFF to work - ) - - def __init__(self, redis, logger, console_logger=None): - """ - Each app should have this __init__ to set up Redis and logging. - :param redis: - :param logger: - :param console_logger: - """ - super().__init__(redis, logger, console_logger) - - def router(self): - return "This action should be skipped" - - def base64_conversion(self, string, operation): - - if operation == "encode": - encoded_bytes = base64.b64encode(string.encode("utf-8")) - encoded_string = str(encoded_bytes, "utf-8") - return encoded_string - - elif operation == "decode": - decoded_bytes = base64.b64decode(string.encode("utf-8")) - decoded_string = str(decoded_bytes, "utf-8") - return decoded_string - - # This is an SMS function of Shuffle - def send_sms_shuffle(self, apikey, phone_numbers, body): - targets = [phone_numbers] - if ", " in phone_numbers: - targets = phone_numbers.split(", ") - elif "," in phone_numbers: - targets = phone_numbers.split(",") - - data = {"numbers": targets, "body": body} - - url = "https://shuffler.io/api/v1/functions/sendsms" - headers = {"Authorization": "Bearer %s" % apikey} - return requests.post(url, headers=headers, json=data).text - - # This is an email function of Shuffle - def send_email_shuffle(self, apikey, recipients, subject, body): - targets = [recipients] - if ", " in recipients: - targets = recipients.split(", ") - elif "," in recipients: - targets = recipients.split(",") - - data = {"targets": targets, "body": body, "subject": subject, "type": "alert"} - - url = "https://shuffler.io/api/v1/functions/sendmail" - headers = {"Authorization": "Bearer %s" % apikey} - return requests.post(url, headers=headers, json=data).text - - def repeat_back_to_me(self, call): - return call - - # https://github.com/fhightower/ioc-finder - def parse_file_ioc(self, file_ids, input_type="all"): - def parse(data): - try: - iocs = find_iocs(str(data)) - newarray = [] - for key, value in iocs.items(): - if input_type != "all": - if key not in input_type: - continue - if len(value) > 0: - for item in value: - if isinstance(value, dict): - for subkey, subvalue in value.items(): - if len(subvalue) > 0: - for subitem in subvalue: - data = { - "data": subitem, - "data_type": "%s_%s" - % (key[:-1], subkey), - } - if data not in newarray: - newarray.append(data) - else: - data = {"data": item, "data_type": key[:-1]} - if data not in newarray: - newarray.append(data) - for item in newarray: - if "ip" in item["data_type"]: - item["data_type"] = "ip" - return {"success": True, "items": newarray} - except Exception as excp: - return {"success": False, "message": "{}".format(excp)} - - if input_type == "": - input_type = "all" - else: - input_type = input_type.split(",") - - try: - file_ids = eval(file_ids) # nosec - except SyntaxError: - file_ids = file_ids - except NameError: - file_ids = file_ids - - return_value = None - if type(file_ids) == str: - return_value = parse(self.get_file(file_ids)["data"]) - elif type(file_ids) == list and type(file_ids[0]) == str: - return_value = [ - parse(self.get_file(file_id)["data"]) for file_id in file_ids - ] - elif ( - type(file_ids) == list - and type(file_ids[0]) == list - and type(file_ids[0][0]) == str - ): - return_value = [ - [parse(self.get_file(file_id2)["data"]) for file_id2 in file_id] - for file_id in file_ids - ] - else: - return "Invalid input" - return return_value - - # https://github.com/fhightower/ioc-finder - def parse_ioc(self, input_string, input_type="all"): - if input_type == "": - input_type = "all" - else: - input_type = input_type.split(",") - - iocs = find_iocs(input_string) - newarray = [] - for key, value in iocs.items(): - if input_type != "all": - if key not in input_type: - continue - - if len(value) > 0: - for item in value: - # If in here: attack techniques. Shouldn't be 3 levels so no - # recursion necessary - if isinstance(value, dict): - for subkey, subvalue in value.items(): - if len(subvalue) > 0: - for subitem in subvalue: - data = { - "data": subitem, - "data_type": "%s_%s" % (key[:-1], subkey), - } - if data not in newarray: - newarray.append(data) - else: - data = {"data": item, "data_type": key[:-1]} - if data not in newarray: - newarray.append(data) - - # Reformatting IP - for item in newarray: - if "ip" in item["data_type"]: - item["data_type"] = "ip" - try: - item["is_private_ip"] = ipaddress.ip_address(item["data"]).is_private - except: - print("Error parsing %s" % ip) - pass - - try: - newarray = json.dumps(newarray) - except json.decoder.JSONDecodeError as e: - return "Failed to parse IOC's: %s" % e - - return newarray - - def parse_list(self, items, splitter="\n"): - if splitter == "": - splitter = "\n" - - splititems = items.split(splitter) - - return str(splititems) - - def get_length(self, item): - if item.startswith("[") and item.endswith("]"): - try: - item = item.replace("'", '"', -1) - item = json.loads(item) - except json.decoder.JSONDecodeError as e: - print("Parse error: %s" % e) - pass - - return str(len(item)) - - def delete_json_keys(self, json_object, keys): - splitdata = [keys] - if ", " in keys: - splitdata = keys.split(", ") - elif "," in keys: - splitdata = keys.split(",") - - for key in splitdata: - key = key.strip() - try: - del json_object[key] - except: - print("Key %s doesn't exist" % key) - - return json_object - - def translate_value(self, input_data, translate_from, translate_to, else_value=""): - splitdata = [translate_from] - if ", " in translate_from: - splitdata = translate_from.split(", ") - elif "," in translate_from: - splitdata = translate_from.split(",") - - if isinstance(input_data, list) or isinstance(input_data, dict): - input_data = json.dumps(input_data) - - to_return = input_data - if isinstance(input_data, str): - found = False - for item in splitdata: - item = item.strip() - if item in input_data: - input_data = input_data.replace(item, translate_to) - found = True - - if not found and len(else_value) > 0: - input_data = else_value - - if input_data.lower() == "false": - return False - elif input_data.lower() == "true": - return True - - return input_data - - def map_value(self, input_data, mapping): - - mapping = json.loads(mapping) - print(f"Got mapping {json.dumps(mapping, indent=2)}") - - # Get value if input_data in map, otherwise return original input_data - output_data = mapping.get(input_data, input_data) - print(f"Mapping {input_data} to {output_data}") - - return output_data - - def regex_replace( - self, input_data, regex, replace_string="", ignore_case="False" - ): - - print("=" * 80) - print(f"Regex: {regex}") - print(f"replace_string: {replace_string}") - print("=" * 80) - - if ignore_case.lower().strip() == "true": - return re.sub(regex, replace_string, input_data, flags=re.IGNORECASE) - else: - return re.sub(regex, replace_string, input_data) - - def execute_python(self, code, shuffle_input): - print("Run with shuffle_data %s" % shuffle_input) - print("And python code %s" % code) - # Write the code to a file, then jdjd - exec(code) # nosec - - # 1. Take the data into a file - # 2. Subprocess execute file? - - # May be necessary - # compile() - - return "Some return: %s" % shuffle_input - - def execute_bash(self, code, shuffle_input): - process = subprocess.Popen( - code, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - shell=True, # nosec - ) - stdout = process.communicate() - item = "" - if len(stdout[0]) > 0: - print("Succesfully ran bash!") - item = stdout[0] - else: - print("FAILED to run bash!") - item = stdout[1] - - try: - ret = item.decode("utf-8") - return ret - except Exception: - return item - - return item - - def filter_list(self, input_list, field, check, value, opposite): - print(f"\nRunning function with list {input_list}") - - flip = False - if opposite.lower() == "true": - flip = True - - try: - input_list = eval(input_list) # nosec - except Exception: - try: - input_list = input_list.replace("'", '"', -1) - input_list = json.loads(input_list) - except Exception: - print("Error parsing string to array. Continuing anyway.") - - # Workaround D: - if not isinstance(input_list, list): - return { - "success": False, - "reason": "Error: input isnt a list. Remove # to use this action." - % type(input_list), - "valid": [], - "invalid": [], - } - - input_list = [input_list] - - print("\nRunning with check \"%s\" on list of length %d\n" % (check, len(input_list))) - found_items = [] - new_list = [] - failed_list = [] - for item in input_list: - try: - try: - item = json.loads(item) - except Exception: - pass - - # Support for nested dict key - tmp = item - if field and field.strip() != "": - for subfield in field.split("."): - tmp = tmp[subfield] - - if isinstance(tmp, dict) or isinstance(tmp, list): - try: - tmp = json.dumps(tmp) - except json.decoder.JSONDecodeError as e: - print("FAILED DECODING: %s" % e) - pass - - #print("PRE CHECKS FOR TMP: %") - - # EQUALS JUST FOR STR - if check == "equals": - # Mostly for bools - # value = tmp.lower() - - if str(tmp).lower() == str(value).lower(): - print("APPENDED BECAUSE %s %s %s" % (field, check, value)) - if not flip: - new_list.append(item) - else: - failed_list.append(item) - else: - if flip: - new_list.append(item) - else: - failed_list.append(item) - - # IS EMPTY FOR STR OR LISTS - elif check == "is empty": - if tmp == "[]": - tmp = [] - - if type(tmp) == list and len(tmp) == 0 and not flip: - new_list.append(item) - elif type(tmp) == list and len(tmp) > 0 and flip: - new_list.append(item) - elif type(tmp) == str and not tmp and not flip: - new_list.append(item) - elif type(tmp) == str and tmp and flip: - new_list.append(item) - else: - failed_list.append(item) - - # STARTS WITH = FOR STR OR [0] FOR LIST - elif check == "starts with": - if type(tmp) == list and tmp[0] == value and not flip: - new_list.append(item) - elif type(tmp) == list and tmp[0] != value and flip: - new_list.append(item) - elif type(tmp) == str and tmp.startswith(value) and not flip: - new_list.append(item) - elif type(tmp) == str and not tmp.startswith(value) and flip: - new_list.append(item) - else: - failed_list.append(item) - - # ENDS WITH = FOR STR OR [-1] FOR LIST - elif check == "ends with": - if type(tmp) == list and tmp[-1] == value and not flip: - new_list.append(item) - elif type(tmp) == list and tmp[-1] != value and flip: - new_list.append(item) - elif type(tmp) == str and tmp.endswith(value) and not flip: - new_list.append(item) - elif type(tmp) == str and not tmp.endswith(value) and flip: - new_list.append(item) - else: - failed_list.append(item) - - # CONTAINS FIND FOR LIST AND IN FOR STR - elif check == "contains": - if type(tmp) == list and value.lower() in tmp and not flip: - new_list.append(item) - elif type(tmp) == list and value.lower() not in tmp and flip: - new_list.append(item) - elif ( - type(tmp) == str - and tmp.lower().find(value.lower()) != -1 - and not flip - ): - new_list.append(item) - elif ( - type(tmp) == str - and tmp.lower().find(value.lower()) == -1 - and flip - ): - new_list.append(item) - else: - failed_list.append(item) - elif check == "contains any of": - print("Inside contains any of") - checklist = value.split(",") - print("Checklist and tmp: %s - %s" % (checklist, tmp)) - found = False - for subcheck in checklist: - subcheck = subcheck.strip().lower() - #ext.lower().strip() == value.lower().strip() - if type(tmp) == list and subcheck in tmp and not flip: - new_list.append(item) - found = True - break - elif type(tmp) == list and subcheck not in tmp and flip: - new_list.append(item) - found = True - break - elif (type(tmp) == str and tmp.lower().find(subcheck) != -1 and not flip): - new_list.append(item) - found = True - break - elif (type(tmp) == str and tmp.lower().find(subcheck) == -1 and flip): - new_list.append(item) - found = True - break - - if not found: - failed_list.append(item) - - # CONTAINS FIND FOR LIST AND IN FOR STR - elif check == "field is unique": - #print("FOUND: %s" - if tmp.lower() not in found_items and not flip: - new_list.append(item) - found_items.append(tmp.lower()) - elif tmp.lower() in found_items and flip: - new_list.append(item) - found_items.append(tmp.lower()) - else: - failed_list.append(item) - - #tmp = json.dumps(tmp) - - #for item in new_list: - #if type(tmp) == list and value.lower() in tmp and not flip: - # new_list.append(item) - # found = True - # break - #elif type(tmp) == list and value.lower() not in tmp and flip: - # new_list.append(item) - # found = True - # break - - # CONTAINS FIND FOR LIST AND IN FOR STR - elif check == "contains any of": - checklist = value.split(",") - tmp = tmp.lower() - print("CHECKLIST: %s. Value: %s" % (checklist, tmp)) - found = False - for value in checklist: - if value in tmp and not flip: - new_list.append(item) - found = True - break - elif value not in tmp and flip: - new_list.append(item) - found = True - break - - if not found: - failed_list.append(item) - - elif check == "larger than": - if int(tmp) > int(value) and not flip: - new_list.append(item) - elif int(tmp) > int(value) and flip: - new_list.append(item) - else: - failed_list.append(item) - elif check == "less than": - if int(tmp) < int(value) and not flip: - new_list.append(item) - elif int(tmp) < int(value) and flip: - new_list.append(item) - else: - failed_list.append(item) - - # SINGLE ITEM COULD BE A FILE OR A LIST OF FILES - elif check == "files by extension": - if type(tmp) == list: - file_list = [] - - for file_id in tmp: - filedata = self.get_file(file_id) - _, ext = os.path.splitext(filedata["filename"]) - if ( - ext.lower().strip() == value.lower().strip() - and not flip - ): - file_list.append(file_id) - elif ext.lower().strip() != value.lower().strip() and flip: - file_list.append(file_id) - # else: - # failed_list.append(file_id) - - tmp = item - if field and field.strip() != "": - for subfield in field.split(".")[:-1]: - tmp = tmp[subfield] - tmp[field.split(".")[-1]] = file_list - new_list.append(item) - else: - new_list = file_list - # else: - # failed_list = file_list - - elif type(tmp) == str: - filedata = self.get_file(tmp) - _, ext = os.path.splitext(filedata["filename"]) - if ext.lower().strip() == value.lower().strip() and not flip: - new_list.append(item) - elif ext.lower().strip() != value.lower().strip() and flip: - new_list.append((item, ext)) - else: - failed_list.append(item) - - except Exception as e: - # "Error: %s" % e - print("[WARNING] FAILED WITH EXCEPTION: %s" % e) - failed_list.append(item) - # return - - try: - return json.dumps( - { - "success": True, - "valid": new_list, - "invalid": failed_list, - } - ) - # new_list = json.dumps(new_list) - except json.decoder.JSONDecodeError as e: - return json.dumps( - { - "success": False, - "reason": "Failed parsing filter list output" + e, - } - ) - - return new_list - - #def multi_list_filter(self, input_list, field, check, value): - # input_list = input_list.replace("'", '"', -1) - # input_list = json.loads(input_list) - - # fieldsplit = field.split(",") - # if ", " in field: - # fieldsplit = field.split(", ") - - # valuesplit = value.split(",") - # if ", " in value: - # valuesplit = value.split(", ") - - # checksplit = check.split(",") - # if ", " in check: - # checksplit = check.split(", ") - - # new_list = [] - # for list_item in input_list: - # list_item = json.loads(list_item) - - # index = 0 - # for check in checksplit: - # if check == "equals": - # print( - # "Checking %s vs %s" - # % (list_item[fieldsplit[index]], valuesplit[index]) - # ) - # if list_item[fieldsplit[index]] == valuesplit[index]: - # new_list.append(list_item) - - # index += 1 - - # # "=", - # # "equals", - # # "!=", - # # "does not equal", - # # ">", - # # "larger than", - # # "<", - # # "less than", - # # ">=", - # # "<=", - # # "startswith", - # # "endswith", - # # "contains", - # # "re", - # # "matches regex", - - # try: - # new_list = json.dumps(new_list) - # except json.decoder.JSONDecodeError as e: - # return "Failed parsing filter list output" % e - - # return new_list - - # Gets the file's metadata, e.g. md5 - def get_file_meta(self, file_id): - headers = { - "Authorization": "Bearer %s" % self.authorization, - } - - ret = requests.get( - "%s/api/v1/files/%s?execution_id=%s" - % (self.url, file_id, self.current_execution_id), - headers=headers, - ) - print(f"RET: {ret}") - - return ret.text - - # Use data from AppBase to talk to backend - def delete_file(self, file_id): - headers = { - "Authorization": "Bearer %s" % self.authorization, - } - print("HEADERS: %s" % headers) - - ret = requests.delete( - "%s/api/v1/files/%s?execution_id=%s" - % (self.url, file_id, self.current_execution_id), - headers=headers, - ) - return ret.text - - def get_file_value(self, filedata): - if filedata is None: - return "File is empty?" - - print("INSIDE APP DATA: %s" % filedata) - return "%s" % filedata["data"].decode() - - def download_remote_file(self, url): - ret = requests.get(url, verify=False) # nosec - filename = url.split("/")[-1] - fileret = self.set_files( - [ - { - "filename": filename, - "data": ret.content, - } - ] - ) - - if len(fileret) > 0: - value = {"success": True, "file_id": fileret[0]} - else: - value = {"success": False, "reason": "No files downloaded"} - - return value - - def extract_archive(self, file_ids, fileformat="zip", password=None): - try: - return_data = {"success": False, "files": []} - - try: - file_ids = eval(file_ids) # nosec - except SyntaxError: - file_ids = file_ids - - print("IDS: %s" % file_ids) - items = file_ids if type(file_ids) == list else file_ids.split(",") - for file_id in items: - - item = self.get_file(file_id) - return_ids = None - - print("Working with fileformat %s" % fileformat) - with tempfile.TemporaryDirectory() as tmpdirname: - - # Get archive and save phisically - with open(os.path.join(tmpdirname, "archive"), "wb") as f: - f.write(item["data"]) - - # Grab files before, upload them later - to_be_uploaded = [] - - # Zipfile for zipped archive - if fileformat.strip().lower() == "zip": - try: - with zipfile.ZipFile( - os.path.join(tmpdirname, "archive") - ) as z_file: - if password: - z_file.setpassword(bytes(password.encode())) - for member in z_file.namelist(): - filename = os.path.basename(member) - if not filename: - continue - source = z_file.open(member) - to_be_uploaded.append( - {"filename": source.name, "data": source.read()} - ) - return_data["success"] = True - except (zipfile.BadZipFile, Exception): - return_data["files"].append( - { - "success": False, - "file_id": file_id, - "filename": item["filename"], - "message": "File is not a valid zip archive", - } - ) - - continue - - elif fileformat.strip().lower() == "rar": - try: - with rarfile.RarFile( - os.path.join(tmpdirname, "archive") - ) as z_file: - if password: - z_file.setpassword(password) - for member in z_file.namelist(): - filename = os.path.basename(member) - if not filename: - continue - source = z_file.open(member) - to_be_uploaded.append( - {"filename": source.name, "data": source.read()} - ) - return_data["success"] = True - except Exception: - return_data["files"].append( - { - "success": False, - "file_id": file_id, - "filename": item["filename"], - "message": "File is not a valid rar archive", - } - ) - continue - - elif fileformat.strip().lower() == "tar": - try: - with tarfile.open( - os.path.join(tmpdirname, "archive"), mode="r" - ) as z_file: - for member in z_file.getnames(): - member_files = z_file.extractfile(member) - to_be_uploaded.append( - { - "filename": member, - "data": member_files.read(), - } - ) - return_data["success"] = True - except Exception as e: - return_data["files"].append( - { - "success": False, - "file_id": file_id, - "filename": item["filename"], - "message": e, - } - ) - continue - elif fileformat.strip().lower() == "tar.gz": - try: - with tarfile.open( - os.path.join(tmpdirname, "archive"), mode="r:gz" - ) as z_file: - for member in z_file.getnames(): - member_files = z_file.extractfile(member) - to_be_uploaded.append( - { - "filename": member, - "data": member_files.read(), - } - ) - return_data["success"] = True - except Exception as e: - return_data["files"].append( - { - "success": False, - "file_id": file_id, - "filename": item["filename"], - "message": e, - } - ) - continue - - elif fileformat.strip().lower() == "7zip": - try: - with py7zr.SevenZipFile( - os.path.join(tmpdirname, "archive"), - mode="r", - password=password if password else None, - ) as z_file: - for filename, source in z_file.readall().items(): - # Removes paths - filename = filename.split("/")[-1] - to_be_uploaded.append( - { - "filename": item["filename"], - "data": source.read(), - } - ) - return_data["success"] = True - except Exception: - return_data["files"].append( - { - "success": False, - "file_id": file_id, - "filename": item["filename"], - "message": "File is not a valid 7zip archive", - } - ) - continue - else: - return "No such format: %s" % fileformat - - if len(to_be_uploaded) > 0: - return_ids = self.set_files(to_be_uploaded) - return_data["files"].append( - { - "success": True, - "file_id": file_id, - "filename": item["filename"], - "file_ids": return_ids, - } - ) - else: - return_data["files"].append( - { - "success": False, - "file_id": file_id, - "filename": item["filename"], - "message": "Archive is empty", - } - ) - - return return_data - - except Exception as excp: - return {"success": False, "message": "%s" % excp} - - def inflate_archive(self, file_ids, fileformat, name, password=None): - - try: - # TODO: will in future support multiple files instead of string ids? - file_ids = file_ids.split() - print("picking {}".format(file_ids)) - - # GET all items from shuffle - items = [self.get_file(file_id) for file_id in file_ids] - - if len(items) == 0: - return "No file to inflate" - - # Dump files on disk, because libs want path :( - with tempfile.TemporaryDirectory() as tmpdir: - paths = [] - print("Number 1") - for item in items: - with open(os.path.join(tmpdir, item["filename"]), "wb") as f: - f.write(item["data"]) - paths.append(os.path.join(tmpdir, item["filename"])) - - # Create archive temporary - print("{} items to inflate".format(len(items))) - with tempfile.NamedTemporaryFile() as archive: - - if fileformat == "zip": - archive_name = "archive.zip" if not name else name - pyminizip.compress_multiple( - paths, [], archive.name, password, 5 - ) - - elif fileformat == "7zip": - archive_name = "archive.7z" if not name else name - with py7zr.SevenZipFile( - archive.name, - "w", - password=password if len(password) > 0 else None, - ) as sz_archive: - for path in paths: - sz_archive.write(path) - - else: - return "Format {} not supported".format(fileformat) - - return_id = self.set_files( - [{"filename": archive_name, "data": open(archive.name, "rb")}] - ) - - if len(return_id) == 1: - # Returns the first file's ID - return {"success": True, "id": return_id[0]} - else: - return { - "success": False, - "message": "Upload archive returned {}".format(return_id), - } - - except Exception as excp: - return {"success": False, "message": excp} - - def add_list_to_list(self, list_one, list_two): - try: - list_one = json.loads(list_one) - except json.decoder.JSONDecodeError as e: - print("Failed to parse list1 as json: %s" % e) - return "List one is not a valid list: %s" % list_one - - try: - list_two = json.loads(list_two) - except json.decoder.JSONDecodeError as e: - print("Failed to parse list2 as json: %s" % e) - return "List two is not a valid list: %s" % list_two - - for item in list_two: - list_one.append(item) - - return list_one - - def diff_lists(self, list_one, list_two): - try: - list_one = json.loads(list_one) - except json.decoder.JSONDecodeError as e: - print("Failed to parse list1 as json: %s" % e) - - try: - list_two = json.loads(list_two) - except json.decoder.JSONDecodeError as e: - print("Failed to parse list2 as json: %s" % e) - - def diff(li1, li2): - return list(set(li1) - set(li2)) + list(set(li2) - set(li1)) - - return diff(list_one, list_two) - - def merge_lists(self, list_one, list_two, set_field="", sort_key_list_one="", sort_key_list_two=""): - try: - list_one = json.loads(list_one) - except json.decoder.JSONDecodeError as e: - print("Failed to parse list1 as json: %s" % e) - - try: - list_two = json.loads(list_two) - except json.decoder.JSONDecodeError as e: - print("Failed to parse list2 as json: %s" % e) - - if len(list_one) != len(list_two): - return {"success": False, "message": "Lists length must be the same. %d vs %d" % (len(list_one), len(list_two))} - - #result = json.loads(input_data) - print(list_one) - print(list_two) - print(set_field) - print("START: ") - - if len(sort_key_list_one) > 0: - print("Sort 1 %s by key: %s" % (list_one, sort_key_list_one)) - try: - list_one = sorted(list_one, key=lambda k: k.get(sort_key_list_one), reverse=True) - except: - print("Failed to sort list one") - pass - - if len(sort_key_list_two) > 0: - #print("Sort 2 %s by key: %s" % (list_two, sort_key_list_two)) - try: - list_two = sorted(list_two, key=lambda k: k.get(sort_key_list_two), reverse=True) - except: - print("Failed to sort list one") - pass - - for i in range(len(list_one)): - #print(list_two[i]) - if isinstance(list_two[i], dict): - for key, value in list_two[i].items(): - list_one[i][key] = value - elif isinstance(list_two[i], str) or isinstance(list_two[i], int) or isinstance(list_two[i], bool): - print("IN SETTER FOR %s" % list_two[i]) - if len(set_field) == 0: - return "Define a JSON key to set for List two (Set Field)" - - list_one[i][set_field] = list_two[i] - - return list_one - - def xml_json_convertor(self, convertto, data): - try: - if convertto == "json": - ans = xmltodict.parse(data) - json_data = json.dumps(ans) - return json_data - else: - ans = readfromstring(data) - return json2xml.Json2xml(ans, wrapper="all", pretty=True).to_xml() - except Exception as e: - return e - - def date_to_epoch(self, input_data, date_field, date_format): - - print( - "Executing with {} on {} with format {}".format( - input_data, date_field, date_format - ) - ) - - result = json.loads(input_data) - - # https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior - epoch = datetime.datetime.strptime(result[date_field], date_format).strftime( - "%s" - ) - result["epoch"] = epoch - return result - - def compare_relative_date( - self, input_data, date_format, equality_test, offset, units, direction - ): - - if input_data == "None": - return False - - print("Converting input date.") - - if date_format != "%s": - input_dt = datetime.datetime.strptime(input_data, date_format) - else: - input_dt = datetime.datetime.utcfromtimestamp(float(input_data)) - - offset = int(offset) - if units == "seconds": - delta = datetime.timedelta(seconds=offset) - elif units == "minutes": - delta = datetime.timedelta(minutes=offset) - elif units == "hours": - delta = datetime.timedelta(hours=offset) - elif units == "days": - delta = datetime.timedelta(days=offset) - - utc_format = date_format - if utc_format.endswith("%z"): - utc_format = utc_format.replace("%z", "Z") - - if date_format != "%s": - formatted_dt = datetime.datetime.strptime( - datetime.datetime.utcnow().strftime(utc_format), date_format - ) - else: - formatted_dt = datetime.datetime.utcnow() - - print("Formatted time is: {}".format(formatted_dt)) - if direction == "ago": - comparison_dt = formatted_dt - delta - else: - comparison_dt = formatted_dt + delta - print("{} {} {} is {}".format(offset, units, direction, comparison_dt)) - - diff = (input_dt - comparison_dt).total_seconds() - print( - "Difference between {} and {} is {}".format(input_data, comparison_dt, diff) - ) - result = False - if equality_test == ">": - result = 0 > diff - if direction == "ahead": - result = not (result) - elif equality_test == "<": - result = 0 < diff - if direction == "ahead": - result = not (result) - elif equality_test == "=": - result = diff == 0 - elif equality_test == "!=": - result = diff != 0 - elif equality_test == ">=": - result = 0 >= diff - if direction == "ahead" and diff != 0: - result = not (result) - elif equality_test == "<=": - result = 0 <= diff - if direction == "ahead" and diff != 0: - result = not (result) - - print( - "At {}, is {} {} than {} {} {}? {}".format( - formatted_dt, - input_data, - equality_test, - offset, - units, - direction, - result, - ) - ) - - return result - - def run_math_operation(self, operation): - print("Operation: %s" % operation) - result = eval(operation) - return result - - def escape_html(self, input_data, field_name): - - mapping = json.loads(input_data) - print(f"Got mapping {json.dumps(mapping, indent=2)}") - - result = markupsafe.escape(mapping[field_name]) - print(f"Mapping {input_data} to {result}") - - mapping[field_name] = result - return mapping - - def get_cache_value(self, key): - org_id = self.full_execution["workflow"]["execution_org"]["id"] - url = "%s/api/v1/orgs/%s/get_cache" % (self.url, org_id) - data = { - "workflow_id": self.full_execution["workflow"]["id"], - "execution_id": self.current_execution_id, - "authorization": self.authorization, - "org_id": org_id, - "key": key, - } - - value = requests.post(url, json=data) - try: - allvalues = value.json() - print("VAL1: ", allvalues) - allvalues["key"] = key - print("VAL2: ", allvalues) - - try: - parsedvalue = json.loads(allvalues["value"]) - allvalues["value"] = parsedvalue - except: - print("Parsing of value as JSON failed") - pass - - return json.dumps(allvalues) - except: - print("Value couldn't be parsed, or json dump of value failed") - return value.text - - # FIXME: Add option for org only & sensitive data (not to be listed) - def set_cache_value(self, key, value): - org_id = self.full_execution["workflow"]["execution_org"]["id"] - url = "%s/api/v1/orgs/%s/set_cache" % (self.url, org_id) - data = { - "workflow_id": self.full_execution["workflow"]["id"], - "execution_id": self.current_execution_id, - "authorization": self.authorization, - "org_id": org_id, - "key": key, - "value": str(value), - } - - response = requests.post(url, json=data) - try: - allvalues = response.json() - allvalues["key"] = key - allvalues["value"] = str(value) - return json.dumps(allvalues) - except: - print("Value couldn't be parsed") - return response.text - - def convert_json_to_tags(self, json_object, split_value=", ", include_key=True, lowercase=True): - try: - json_object = json.loads(json_object) - except json.decoder.JSONDecodeError as e: - print("Failed to parse list2 as json: %s. Type: %s" % (e, type(json_object))) - - if isinstance(lowercase, str) and lowercase.lower() == "true": - lowercase = True - else: - lowercase = False - - if isinstance(include_key, str) or include_key.lower() == "true": - include_key = True - else: - include_key = False - - parsedstring = [] - for key, value in json_object.items(): - print("KV: %s:%s" % (key, value)) - if isinstance(value, str) or isinstance(value, int) or isinstance(value, bool): - if include_key == True: - parsedstring.append("%s:%s" % (key, value)) - else: - parsedstring.append("%s" % (value)) - else: - print("Can't handle type %s" % type(value)) - - fullstring = split_value.join(parsedstring) - if lowercase == True: - fullstring = fullstring.lower() - - return fullstring - - def cidr_ip_match(self, ip, networks): - print("Executing with\nIP: {},\nNetworks: {}".format(ip, networks)) - - try: - networks = json.loads(networks) - except json.decoder.JSONDecodeError as e: - print("Failed to parse networks list as json: {}. Type: {}".format( - e, type(networks) - )) - return "Networks is not a valid list: {}".format(networks) - - try: - ip_networks = list(map(ipaddress.ip_network, networks)) - ip_address = ipaddress.ip_address(ip) - except ValueError as e: - return "IP or some networks are not in valid format.\nError: {}".format(e) - - matched_networks = list(filter(lambda net: (ip_address in net), ip_networks)) - - result = {} - result['networks'] = list(map(str, matched_networks)) - result['is_contained'] = True if len(result['networks']) > 0 else False - - return json.dumps(result) - -if __name__ == "__main__": - Tools.run() diff --git a/shuffle-tools/1.1.0/Dockerfile b/shuffle-tools/1.1.0/Dockerfile deleted file mode 100644 index 5c1a8af4..00000000 --- a/shuffle-tools/1.1.0/Dockerfile +++ /dev/null @@ -1,27 +0,0 @@ -# Base our app image off of the WALKOFF App SDK image -FROM frikky/shuffle:app_sdk as base - -# We're going to stage away all of the bloat from the build tools so lets create a builder stage -FROM base as builder - -# Install all alpine build tools needed for our pip installs -RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev git - -# Install all of our pip packages in a single directory that we can copy to our base image later -RUN mkdir /install -WORKDIR /install -COPY requirements.txt /requirements.txt -RUN pip install --no-cache-dir --prefix="/install" -r /requirements.txt - -# Switch back to our base image and copy in all of our built packages and source code -FROM base -COPY --from=builder /install /usr/local -COPY src /app - -# Install any binary dependencies needed in our final image -# RUN apk --no-cache add --update my_binary_dependency -RUN apk --no-cache add jq git curl - -# Finally, lets run our app! -WORKDIR /app -CMD ["python", "app.py", "--log-level", "DEBUG"] diff --git a/shuffle-tools/1.1.0/api.yaml b/shuffle-tools/1.1.0/api.yaml deleted file mode 100644 index 464c1d02..00000000 --- a/shuffle-tools/1.1.0/api.yaml +++ /dev/null @@ -1,950 +0,0 @@ ---- -app_version: 1.1.0 -name: Shuffle Tools -description: A tool app for Shuffle. Gives access to most missing features along with Liquid. -tags: - - Testing - - Shuffle -categories: - - Testing - - Shuffle -contact_info: - name: "@frikkylikeme" - url: https://shuffler.io - email: frikky@shuffler.io -actions: - - name: repeat_back_to_me - description: Repeats the call parameter - parameters: - - name: call - description: The message to repeat - required: true - multiline: true - example: "REPEATING: Hello world" - schema: - type: string - returns: - schema: - type: string - - name: router - description: Reroutes information between different nodes - returns: - schema: - type: string - - name: check_cache_contains - description: Checks Shuffle cache whether a user-provided key contains a value. Returns ALL the values previously appended. - parameters: - - name: key - description: The key to get - required: true - multiline: false - example: "alert_ids" - schema: - type: string - - name: value - description: The value to check for and append if applicable - required: true - multiline: false - example: "1208301599081" - schema: - type: string - - name: append - description: Whether to auto-append the value if it doesn't exist in the cache - required: true - options: - - true - - false - multiline: false - example: "timestamp" - schema: - type: string - - name: get_cache_value - description: Get a value saved to your organization in Shuffle - parameters: - - name: key - description: The key to get - required: true - multiline: false - example: "timestamp" - schema: - type: string - returns: - schema: - type: string - - name: set_cache_value - description: Set a value to be saved to your organization in Shuffle. - parameters: - - name: key - description: The key to set the value for - required: true - multiline: false - example: "timestamp" - schema: - type: string - - name: value - description: The value to set - required: true - multiline: true - example: "1621959545" - schema: - type: string - returns: - schema: - type: string - - name: send_sms_shuffle - description: Send an SMS from Shuffle - parameters: - - name: apikey - description: Your https://shuffler.io organization apikey - multiline: false - example: "https://shuffler.io apikey" - required: true - schema: - type: string - - name: phone_numbers - description: The receivers of the SMS - multiline: false - example: "+4741323535,+8151023022" - required: true - schema: - type: string - - name: body - description: The SMS to add to the numbers - multiline: true - example: "This is an alert from Shuffle :)" - required: true - schema: - type: string - returns: - schema: - type: string - - name: send_email_shuffle - description: Send an email from Shuffle - parameters: - - name: apikey - description: Your https://shuffler.io organization apikey - multiline: false - example: "https://shuffler.io apikey" - required: true - schema: - type: string - - name: recipients - description: The recipients of the email - multiline: false - example: "test@example.com,frikky@shuffler.io" - required: true - schema: - type: string - - name: subject - description: The subject to use - multiline: false - example: "SOS this is an alert :o" - required: true - schema: - type: string - - name: body - description: The body to add to the email - multiline: true - example: "This is an email alert from Shuffler.io :)" - required: true - schema: - type: string - - name: attachments - description: The ID of files in Shuffle to add as attachments - multiline: true - example: "file_id1,file_id2,file_id3" - required: false - schema: - type: string - returns: - schema: - type: string - - name: filter_list - description: Takes a list and filters based on your data - skip_multicheck: true - parameters: - - name: input_list - description: The list to check - required: true - multiline: false - example: '[{"data": "1.2.3.4"}, {"data": "1.2.3.5"}]' - schema: - type: string - - name: field - description: The field to check - required: false - multiline: false - example: "data" - schema: - type: string - - name: check - description: Type of check - required: true - example: "equals" - options: - - equals - - 'larger than' - - 'less than' - - is empty - - contains - - contains any of - - starts with - - ends with - - field is unique - - files by extension - schema: - type: string - - name: value - description: The value to check with - required: false - multiline: false - example: "1.2.3.4" - schema: - type: string - - name: opposite - description: Whether to add or to NOT add - required: true - options: - - False - - True - multiline: false - example: "false" - schema: - type: string - returns: - schema: - type: string - #- name: multi_list_filter - # description: Takes a list and filters based on your data - # skip_multicheck: true - # parameters: - # - name: input_list - # description: The list to check - # required: true - # multiline: false - # example: '[{"data": "1.2.3.4"}, {"data": "1.2.3.5"}]' - # schema: - # type: string - # - name: field - # description: The field to check - # required: true - # multiline: false - # example: "data" - # schema: - # type: string - # - name: check - # description: Type of check - # required: true - # example: "equals,equals" - # schema: - # type: string - # - name: value - # description: The value to check with - # required: true - # multiline: false - # example: "1.2.3.4" - # schema: - # type: string - # returns: - # schema: - # type: string - - name: parse_ioc - description: Parse IOC's based on https://github.com/fhightower/ioc-finder - parameters: - - name: input_string - description: The string to check - required: true - multiline: true - example: "123ijq192.168.3.6kljqwiejs8 https://shuffler.io" - schema: - type: string - - name: input_type - description: The string to check - required: false - multiline: false - example: "md5s" - schema: - type: string - returns: - schema: - type: string - - name: parse_file_ioc - description: Parse IOC's based on https://github.com/fhightower/ioc-finder - parameters: - - name: file_ids - description: The shuffle file to check - required: true - multiline: false - schema: - type: string - - name: input_type - description: The string to check - required: false - multiline: false - example: "md5s" - schema: - type: string - returns: - schema: - type: string - - name: translate_value - description: Takes a list of values and translates it in your input data - parameters: - - name: input_data - description: The input data to use - required: true - multiline: true - example: Hello this is an md5 - schema: - type: string - - name: translate_from - description: The source items to look for - required: true - multiline: false - example: sha256,md5,sha1 - schema: - type: string - - name: translate_to - description: The destination data to change to - required: true - multiline: true - example: hash - schema: - type: string - - name: else_value - description: The value to set if it DOESNT match. Default to nothing. - required: false - multiline: false - example: - schema: - type: string - returns: - schema: - type: string - - name: map_value - description: Takes a mapping dictionary and translates the input data. This is a search and replace for multiple fields. - parameters: - - name: input_data - description: The input data to use - required: true - multiline: true - example: $exec.field1 - schema: - type: string - - name: mapping - description: The mapping dictionary - required: true - multiline: true - example: | - { - "Low": 1, - "Medium": 2, - "High": 3, - } - schema: - type: string - returns: - schema: - type: string - - name: regex_capture_group - description: Returns objects matching the capture group(s) - parameters: - - name: input_data - description: The input data to use - required: true - multiline: true - example: This is some text a domain that is with.com - schema: - type: string - - name: regex - description: Your regular expression - multiline: false - example: "some text <[a-zA-Z0-9.]+> a domain" - required: true - schema: - type: string - returns: - schema: - type: string - - name: regex_replace - description: Replace all instances matching a regular expression - parameters: - - name: input_data - description: The input data to use - required: true - multiline: true - example: This is some text a domain that is with.com - schema: - type: string - - name: regex - description: Your regular expression - multiline: false - example: "some text <[a-zA-Z0-9.]+> a domain" - required: true - schema: - type: string - - name: replace_string - description: Replacement string (capture groups with \1 \2) - multiline: true - example: "some text a domain" - required: false - schema: - type: string - - name: ignore_case - description: "Make regex case insensitive (Default: False)" - multiline: false - options: - - false - - true - example: "False" - required: false - schema: - type: string - returns: - schema: - type: string - - name: parse_list - description: Parses a list and returns it as a json object - parameters: - - name: items - description: List of items - required: true - multiline: true - example: shuffler.io,test.com,test.no - schema: - type: string - - name: splitter - description: The splitter to use - required: false - multiline: false - example: "," - schema: - type: string - returns: - schema: - type: string - - name: execute_bash - description: Runs bash with the data input - parameters: - - name: code - description: The code to run - required: true - multiline: true - example: echo "Hello" - schema: - type: string - - name: shuffle_input - description: Alternative data to add - required: false - multiline: true - example: '{"data": "Hello world"}' - schema: - type: string - - name: execute_python - description: Runs python with the data input. Any prints will be returned. - parameters: - - name: code - description: The code to run. Can be a file ID from within Shuffle. - required: true - multiline: true - example: print("hello world") - schema: - type: string - - name: get_file_value - description: This function is made for reading file(s), printing their data - parameters: - - name: filedata - description: The files - required: true - multiline: false - example: "a2f89576-a9ec-479e-8c83-da69f468c90a" - schema: - type: string - returns: - schema: - type: string - - name: create_file - description: Returns uploaded file data - parameters: - - name: filename - description: - required: true - multiline: false - example: "test.csv" - schema: - type: string - - name: data - description: - required: true - multiline: true - example: "EventID,username\n4137,frikky" - schema: - type: string - - name: download_remote_file - description: Downloads a file from a URL - parameters: - - name: url - description: - required: true - multiline: false - example: "https://secure.eicar.org/eicar.com.txt" - schema: - type: string - returns: - schema: - type: string - - name: get_file_meta - description: Gets the file meta - parameters: - - name: file_id - description: - required: true - multiline: false - example: "" - schema: - type: string - returns: - schema: - type: string - - name: delete_file - description: Deletes a file based on ID - parameters: - - name: file_id - description: - required: true - multiline: false - example: "Some data to put in the file" - schema: - type: string - returns: - schema: - type: string - - name: extract_archive - description: Extract compressed files, return file ids - parameters: - - name: file_ids - description: - required: true - multiline: false - schema: - type: string - - name: fileformat - description: - required: true - multiline: false - options: - - zip - - rar - - 7zip - - tar - - tar.gz - schema: - type: string - - name: password - description: - required: false - multiline: false - schema: - type: string - returns: - schema: - type: string - - name: inflate_archive - description: Compress files in archive, return archive's file id - parameters: - - name: file_ids - description: - required: true - multiline: true - schema: - type: string - - name: fileformat - description: - required: true - multiline: false - options: - - zip - - 7zip - schema: - type: string - - name: name - description: - required: false - multiline: false - schema: - type: string - - name: password - description: - required: false - multiline: false - schema: - type: string - returns: - schema: - type: string - - name: xml_json_convertor - description: Converts xml to json and vice versa - parameters: - - name: convertto - required: true - multiline: false - options: - - json - - xml - schema: - type: string - - name: data - description: - required: true - multiline: false - example: 'xml data / json data' - schema: - type: string - returns: - schema: - type: string - - name: date_to_epoch - description: Converts a date field with a given format to an epoch time - parameters: - - name: input_data - description: The input data to use - required: true - multiline: true - example: 2010-11-04T04:15:22.123Z - schema: - type: dict - - name: date_field - description: The field containing the date to parse - required: true - multiline: false - example: currentDateTime - schema: - type: string - - name: date_format - # yamllint disable-line rule:line-length - description: The datetime format of the field to parse (strftime format). - required: true - multiline: false - example: '%Y-%m-%dT%H:%M:%s.%f%Z' - schema: - type: string - returns: - schema: - type: dict - - name: compare_relative_date - # yamllint disable-line rule:line-length - description: Compares an input date to a relative date and returns a True/False result - parameters: - - name: input_data - description: The input data to use - required: true - multiline: true - example: 2010-11-04T04:15:22.123Z - schema: - type: string - - name: date_format - description: The format of the input date field (strftime format) - required: true - multiline: false - example: '%Y-%m-%dT%H:%M:%S.%f%Z' - options: - - '%Y-%m-%dT%H:%M%z' - - '%Y-%m-%dT%H:%M:%SZ' - - '%Y-%m-%dT%H:%M:%S%Z' - - '%Y-%m-%dT%H:%M:%S%z' - - '%Y-%m-%dT%H:%M:%S.%f%z' - - '%Y-%m-%d' - - '%H:%M:%S' - - '%s' - schema: - type: string - - name: equality_test - description: How to compare the input date and offset date - required: true - multiline: false - example: '>' - options: - - '>' - - '<' - - '=' - - '!=' - - '>=' - - '<=' - schema: - type: string - - name: offset - description: Numeric offset from current time - required: true - multiline: false - example: 60 - schema: - type: string - - name: units - description: The units of the provided value - required: true - multiline: false - example: 'seconds' - options: - - seconds - - minutes - - hours - - days - schema: - type: string - - name: direction - description: Whether the comparison should be in the past or future - required: true - multiline: false - example: 'ago' - options: - - ago - - ahead - schema: - type: string - returns: - schema: - type: strings - - name: add_list_to_list - description: Adds items of second list (list_two) to the first one (list_one). Can also append a single item (dict) to a list. - parameters: - - name: list_one - description: The first list - multiline: true - example: "{'key': 'value'}" - required: true - schema: - type: string - - name: list_two - description: The second list to use - multiline: true - required: true - example: "{'key2': 'value2'}" - schema: - type: string - - name: merge_lists - description: Merges two lists of same type AND length. - parameters: - - name: list_one - description: The first list - multiline: true - example: "{'key': 'value'}" - required: true - schema: - type: string - - name: list_two - description: The second list to use - multiline: true - required: true - example: "{'key2': 'value2'}" - schema: - type: string - - name: set_field - description: If items in list 2 are strings, but first is JSON, sets the values to the specified key. Defaults to key "new_shuffle_key" - required: false - example: "json_key" - schema: - type: string - - name: sort_key_list_one - description: Sort by this key before using list one for merging - required: false - example: "json_key" - schema: - type: string - - name: sort_key_list_two - description: Sort by this key before using list two for merging - required: false - example: "json_key" - schema: - type: string - - name: diff_lists - description: Diffs two lists of strings or integers and finds what's missing - parameters: - - name: list_one - description: The first list - multiline: true - example: "{'key': 'value'}" - required: true - schema: - type: string - - name: list_two - description: The second list to use - multiline: true - required: true - example: "{'key2': 'value2'}" - schema: - type: string - - name: set_json_key - description: Adds a JSON key to an existing object - parameters: - - name: json_object - description: The object to edit - multiline: true - example: "recipients" - required: true - schema: - type: string - - name: key - description: The object to add - multiline: false - example: "recipients" - required: true - schema: - type: string - - name: value - description: The value to set it to in the JSON object - multiline: true - required: true - example: "frikky@shuffler.io" - schema: - type: string - - name: delete_json_keys - description: Deletes keys in a json object - parameters: - - name: json_object - description: The object to edit - multiline: true - example: "{'key': 'value', 'key2': 'value2', 'key3': 'value3'}" - required: true - schema: - type: string - - name: keys - description: The key(s) to remove - multiline: true - required: true - example: "key, key3" - schema: - type: string - - name: convert_json_to_tags - description: Creates key:value pairs and - parameters: - - name: json_object - description: The object to make into a key:value pair - multiline: true - example: "{'key': 'value', 'key2': 'value2', 'key3': 'value3'}" - required: true - schema: - type: string - - name: split_value - description: The way to split the values. Defaults to comma. - multiline: false - required: false - example: "," - schema: - type: string - - name: include_key - description: Whether it should include the key or not - options: - - true - - false - schema: - type: string - - name: lowercase - description: Whether it should be lowercase or not - options: - - true - - false - schema: - type: string - - name: run_math_operation - description: Takes a math input and gives you the result - parameters: - - name: operation - description: The operation to perform - required: true - multiline: true - example: "5+10" - schema: - type: string - returns: - schema: - type: string - - name: escape_html - description: Performs HTML escaping on a field - parameters: - - name: input_data - description: The input data to use - required: true - multiline: true - example: $exec.field1 - schema: - type: string - - name: field_name - description: The field to HTML escape - required: true - multiline: true - example: my_unsafe_field - schema: - type: string - returns: - schema: - type: string - - name: base64_conversion - description: Encode or decode a Base64 string - parameters: - - name: string - description: string to process - multiline: true - example: "This is a string to be encoded" - required: true - schema: - type: string - - name: operation - description: Choose to encode or decode the string - example: "encode" - required: true - options: - - encode - - decode - schema: - type: string - - name: get_timestamp - description: Gets a timestamp for right now. Default returns an epoch timestamp - parameters: - - name: time_format - description: The format to use - multiline: false - required: True - options: - - epoch - - unix - schema: - type: string - returns: - schema: - type: string - - name: get_hash_sum - description: Returns multiple formats of hashes based on the input value - parameters: - - name: value - description: The value to hash - multiline: false - example: "1.1.1.1" - required: True - schema: - type: string - returns: - schema: - type: string - - name: cidr_ip_match - description: Check if an IP is contained in a CIDR defined network - parameters: - - name: ip - description: IP to check - multiline: false - example: "1.1.1.1" - required: True - schema: - type: string - - name: networks - description: List of network in CIDR format - multiline: true - required: true - example: "['10.0.0.0/8', '192.168.10.0/24']" - schema: - type: string - returns: - schema: - type: string - -# yamllint disable-line rule:line-length -large_image: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAK4AAACuCAYAAACvDDbuAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QA/wD/AP+gvaeTAAAAB3RJTUUH5AgXDjM6hEZGWwAAD+lJREFUeNrtXb/vJTcRH7/v3iVBCqRBiCAQAtHwq4AWRElHwX8AoqbmXwDRpiH/QyQkGoogUSAhKIKUAE1IdSRSREhQQk7c3XtD8X55vePxjNfe3bk3H+nu+96uPf54POtnj8fe8OQX30JwOIxhtzYBh6MGOsPF0z9p2iWwpd8LjX6W5vWUYaiqlBuvLT5b5TQDPlRwmMSAABBg+kCer+XuAeQf4tL9tAxJ/hIfZGSm8rhyEfjytfxr9FeSX+KjvVfipNVpWlaPNhsAEPCS7Ao8FYnRlbO4ksLnjiSQvIanv4FNjwJ5pXIlMq6MQpIqqPnQKQKbjuPDtZlG55o6UHXWtVncZZTbbNBVB1P5dJYguCbJJ1WjOG8PVOioSm5HPrVt1rwuyN+K+PSZnNV1M/MmEFubfFjjU9tmK9XBJ2cOk3DDdZiEG67DJOrGuA7HyvAe12ESAxa73KPrN1z8gUikCCdvcD5NXnpQpA8nNhh9m5Yn4ZMrV8dHV/8a/dRA0x419a3lI9GBtM2GcrGYFXRNUU5TyluTOpdXwqeUt6YOpby9DUTLZylOcRlzdBTf2yV3ZBFOmKSHQh5KpjSSSpqG4s6VkUubqw8W8knTSnWk0Y+2jF5tlmuDUloJn6T8gRVcEpJ+3srChHSNt8RJsq4p+S41LC13KTcu/RJt1pLPKY1Pzhwm4YbrMAk3XIdJTMe4aeCjJhBVk0YiQ1MWZHhLgmO5QNVWfKRlavlIIQnurQmcnaMjSbBxhtMwYUxODpLcl2tUhvPlNE6VkiuoFVLXKT6ZfBjxRIIzOSlgWpLSB8uZ0g3BjeVDlFGEos0mfKKL7CQrY2ES7pM2i/OX22w4/sWReEhEnUOTxx3a+FrawQGZh04/rWe6oJBKo5zT4zLjPHE9ZHym5YzToogzfQcmfLgOhuLF/Sjm2izVDyXnrKtcmmmdaKumf+RyCw5Xn7OmzQaJF0fiEZG6BjXpYUYaSVkaPrXeHe4eVaZEr3Prqrmmrbc2T8lrmOMjn5xJHeJLYkk+PfzNTxOflrwF0EeHbU0Zt2wsW+PTkncB7g5zmMSwzUfS4eDhPa7DJK5jXGorsnZxonbRIbeAoOUjkUvlp+qxFp9YNuWL0nBqsVCkqUsrHQnuX+Nx5/qcJDI0kWgtJh7ihYCN8aG+13DqOXlbWUfD+fN0AUEmp3RcUWlVEwCynb5ssYLnxHViJT6ULCykb8EnzUfpqBWfVAdcnt5tprGhIe10WnjHpB2FtMPWcpM66yXyOad4Lz4Srq34SHhwZfRos1w9Y/jkzGESvj3dYRLe4zpMwg3XYRJuuA6T4M/Hzfk/OGd9OP2HOE2f8wtBlCebJrkfp+Gc3AGmiSiuaVlpwkmajL4osPUm9FMqIzBOJolfjGuzEtdUwWl53Dm7Eh9pzIdps+FiYJyi1N+Rvs/6OLCQBul8Ip8R08ik3EwhLZz1Wv8XmU7ZZqX7OT2gUIB2oaRBm+2ovDm5nM+ulEeiD8yka8UnJ1PCP82r9YWW8iCU5XO8W/PhPmvllNKW7lEyszsgNKuzkspJFZFL15uPtIweq7A1xiKpz1J8tGXP+dE53/fJmcMk6hcgJO8XqokEKi5uYzTG29LqSev95JqyKsoOOxjNpKQBD7VFc5GBJRsi+NQHkkv6+7m/UxTufwLCCy+CbAruyOLDdwEf/uf6vbbNJukzlogZC6wMdhAcM7ohHPawe/GrcO+HPwe4u782G7sIAE9++0vYv/YKwO6usfCaka0etgwXAGB3D8JznwIYnlmbiW0M92FbQy0d+MmZ3Xo5JDDcvuXJ2ZYqtyUuTwuM6nSXctcufHCOZqkjPScXhbIcdeD0XUpfKyNNy8nlyhuozLkM8XxR6pjm7tc4Fdx620I7lWq10JCm0ZanWoBwm3FsBe1WznpadbTg4A9PI2xx7FUKHopQjg7TKqNnpbioIUcFUGUsy1CS8fFYBYdJuOE6TMIN12ESgyiKiwO1bQOJe1w+6p42Etmhwmi6kLZXfC2G9IUj2vulY2wIPrv4onRhIXcRqS0DiWxkhF0uIb37wG22LRCSuVCyekC2GSXj9CG3YyT+krWh+KPAhkTvgGDKqbqnWbBwY+2Pnm3Wy4aMRYc1MuPDvp0skwgAh8PaJGbh5k4kx0f/hce/ewnw/QenXQCTFJDfQy45PzFNn5NHsoPy/u6gzE+nObzz91P9Z+6kWAm2zg6bDMoq8OQxHN78Axze/htAaB1EbQhhdzyfgRqIGoCxoUIjhDuA3ZDpcR0W4C3nMInbNVw7v4oOAsehArVFPL0uOjMM+DlM+pk7t7/BDuwcJsM6gcM7WweOX05nFCHNi12ASRfLo3QaX9O0GWTylOTnZIMwf4YPPTlD4iMm7aZwAGOUf3Rf48wjHNzVOMkKFA8pp0RHZ1mjdihs5R61PWbsWlphgs/E5gptNvFfSLY8QPk7dVbh+UNg8qfnJsZ8Bo0hzF0Y2Nqvc0s+Vbs5YL5OLfPRcorT2hvjtuxyHWZhzHCX6AMcFtB2B0RvtKZqqe6OEYz1uA7HEbdruN7ZmsZtGq4brXnQhlsbLFkDrY9mC9giH41/dSlONfeEIBcgss7nXopInPdkYN95J3XD1bMgkJUNFOxsDNLgyiynhYyX5dnAhnLyhzmO4V7IO8+xyZEgx5UqvJ41rOUTdhBOr2w6KjZc+B1FBkLGVUoAABQEcmPu6rPPw73v/gh2n/wMANYEhAd4/NqvYf/Wn5pEyPW2IUrOzQWSHyHdkEJgN8D97/0Edp/7GgDu9fnDDvD9t+HRqy8BPvxQ9i6xEXUEuPcMDF//Puw+/aVqDewfvA77f/zx9M40e7jNeNw5CDu4++K34e4r36kWcXj3TYDfvwz8D79ml1clDPuxx9FhuUik0rblVihFWLX+7ZFEXE2ioLBNg9fUSRopVsOjJbioskZlDuyAvmflpOWsOUNu/cBQ8jW/1A0np11RG+GjwG36cQHqFWnBcG4Axgx37d/I1uXXcvCnx6BXoQXf3mOAzvVpooJzaOcWdKBH1fZ07dCsFZpNgmfZbaOJ2dxnpwkNFC3C9MBcGxo0OugxwV8LWKm5lg9sFQdszKGhLAla2dCuduuOZcypx+UXdk0OK5e/hXKNTc4cjiPGhtvTX1njI6Z2+vbuKtaKspLooXdkXs1u5yUR7/LdROMsraSSIfTa6pqWodE9Mvla6sCI8d7uUMEXIEzjdg3XYRr2osOePIbDR+9BGO7re78QAD/+AODwpK5sBDg6dGyGAtL1sYnLGDe3+2BNTNycYQf7B2/Aw5d/XB9HejjA4YN3jgHUNQ132MOTv/wG9v98A+CgFBCO/+FH/wJ89PBaSY1OULZzQyQL2skayVwg/7Dk3Ky2IlcEgEcfw/7dt+YJnRP1f9jDoz+/AvM0FU4c1u8mes59e+ZXDhXmPE+tForD+lH73Q6EluiozfaldnzWQUWQzdprPk87lg44nkTKN+DT/10S7lW4VYz8wWucOTAPtl5e4mgfjmu0/b3HdZiEG67DJNxwbxlGhwkAuZeXAJS3Qpfemq7dds1tS5dsbc6dAyQpS5uGe+lKrJLSGUqlCb2GcwUuCxBzt71T2/g7t9mQniofv0yjWOtMYdSLM6Sy0pd5iLdFSQtUyiJtRnjmGOdhqq5bo5WzUXAYzns2Lu2tjaqb0WaTHRBrR9cvEVG4VF3WkLsGnzXqohzjbk3dt4hG/jDDxy8BLL5y5miBZi1wa9vT14dJ0o2qft6/1GhQZ1SV9uJxd3cQ7j+XD7RJ40JK38/XAPKz4ly+OG+KwOTDwn0uDSKEZ58/vgH+hmHLcA97uPvCN+G5H/wMoCaQ/KkAAtzdg/DCZ9cmsipsGS4ce5u7z38DYHhmbTL2YfjBH28DOM80s+MoxllVvfkwKudSbiL0dB0NTya2iGpNYmIzl+/EdexjQ8PEGE4FhdPHMAlbLhcsdWaPnfDEAxQJnbx53TEPJ51j3N7CrEfbSNt+arzXt57X2RBx94LsUGHOGRQtF7Fa8HFQQOabJmc5XQ8b8iAbh0mYNFzvdefD+nRhyPowqWitc2VbRyutGCF18+ilU2mEXWX51zFuKbqlZ/RLy0gixzagiS6sgL2hghuwAywarsMBxgzXO9u2sBzZWHwHRLwrQ5rWYQBIfuwCKnZJEpvEYSg9dRoncnejtdxFbBRLqFQzr5fSudH3nDmOaH26yHIwNcZ1NIZNmwWArYU1Fg8HDLB/7wH879VfAey2Rd0a9g/+2ubUyZUOdAz//umXjT136GPd2cDNnM9bC4Pd1gbOx3WsDh/jOkzCDddhEpcjmKiFhvGLQwDitJNrYTz05H7MS+N56hiq0mbYCfeIj2STb2s+cSJEOrguJ4fScaneOW7kOWZJm4VCmaPFg8wKgcSGuLpzR49Rerm8vIRaaECgvyB1Tbl9qOZoMiykHeVhVoZKwW9N+CSJuPwsH4YY12aTa5TxYyZPpsxSDG/Rhgp1lyxUnK/7UMFhEm64DpNIlnzTAdXcsJml8rdO1yt/K+R45EJUluS9zHaWITuQJb9rsVT+HvuKe+RvhdIIcE3ey4Rj+VDBYRJuuA6TcMN1mMT15SWMZ5h10Oc86+dr50s14QWch7rEh5PHef+psgsyqB0iI2e+hE+pDlpvvkQ/uVUMDfdSnTq12TA58injFUdOMPB5AeiALtHcUrstXrqSINnaoVjxyE5ra1ZipHMsTV2kMiQ8NDw7tdmqQ4WtzNEd9uBjXIdJuOE6TMLoy0sct46KHndNS6d2pW5tp+rW+Jw5rVl2qpP5Oqrcnr52w9RMgbfA8db5tAsp8DGuwyTaGW6DB7ppn9CCzxKnvKz9Kz7j/prUi0cwqQLQDBtvrp5uvMc/Wf00oFAT5FjscbcwMloCt1LPWvTUT41sH+M6TMIN12ESw3UPd8gPtrh7JeTyXvZGn0KD0jSlMms5Sfhw92vkUvXT5tPWt3WbSfjMsSFl3ujlJdy+4xkjnFze+PWrNWXWclqaT6t82vq2bjMJnzk2pMzrQwWHSbjhOkzCDdchxpZchpezwySQvHhiyVMLevPRctXwqeWmfcv5GaVTGKRy557YIHnhpETeoCl05grhbPlL89HK1vCp5darvZbgo+XEwYcKDpNww3WYxC6/U5PY5oun66MzPHH8L05PpqHKghn+TpjyictkZQLPh4u6yeknvXeWU+JD6TDHJ/cbn93Bi8nnDKdJm8EG2+zIZwBudlbjUOYOpj1frClPwyf3OZuXuaEx3lgWZixKxIfZ911rvJO65PRFVmZjbYY+VHDYhBuuwyTccB0mcdkB0cr5z70pW/pm7Bo+LesgqUsrPjVye9WXkqld8FiizRCi6LBWjmTRPGGG/JZ5ejvoa1ai1qwvlWarbeZDBYdJuOE6TKKP4W7xJdFb4+R8ZvH5P852gxhpwOZ9AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIwLTA4LTIzVDE0OjUyOjAwKzAyOjAwetRgVgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyMC0wOC0yM1QxNDo1MTo1OCswMjowMJuxI+oAAAAASUVORK5CYII= diff --git a/shuffle-tools/1.1.0/docker-compose.yml b/shuffle-tools/1.1.0/docker-compose.yml deleted file mode 100644 index e48c6b2f..00000000 --- a/shuffle-tools/1.1.0/docker-compose.yml +++ /dev/null @@ -1,15 +0,0 @@ -version: '3.4' -services: - shuffle-tools: - build: - context: . - dockerfile: Dockerfile -# image: walkoff_registry:5000/walkoff_app_HelloWorld-v1-0 - deploy: - mode: replicated - replicas: 10 - restart_policy: - condition: none - restart: "no" - secrets: - - secret1 diff --git a/shuffle-tools/1.1.0/requirements.txt b/shuffle-tools/1.1.0/requirements.txt deleted file mode 100644 index cd48a310..00000000 --- a/shuffle-tools/1.1.0/requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ -ioc_finder==6.0.1 -py7zr==0.20.2 -rarfile==4.0 -pyminizip==0.2.6 -requests==2.32.4 -xmltodict==0.11.0 -json2xml==5.0.5 -ipaddress==1.0.23 diff --git a/shuffle-tools/1.1.0/src/app.py b/shuffle-tools/1.1.0/src/app.py deleted file mode 100644 index 6da2f2aa..00000000 --- a/shuffle-tools/1.1.0/src/app.py +++ /dev/null @@ -1,1810 +0,0 @@ -import asyncio -import datetime -import json -import time -import markupsafe -import os -import re -import subprocess -import tempfile -import zipfile -import base64 -import ipaddress -import hashlib -from io import StringIO -from contextlib import redirect_stdout -from liquid import Liquid -import liquid - -import py7zr -import pyminizip -import rarfile -import requests -import tarfile - -import xmltodict -from json2xml import json2xml -from json2xml.utils import readfromstring - -from ioc_finder import find_iocs -from walkoff_app_sdk.app_base import AppBase - -import binascii -import struct - -class Tools(AppBase): - """ - An example of a Walkoff App. - Inherit from the AppBase class to have Redis, logging, and console - logging set up behind the scenes. - """ - - __version__ = "1.1.0" - app_name = ( - "Shuffle Tools" # this needs to match "name" in api.yaml for WALKOFF to work - ) - - def __init__(self, redis, logger, console_logger=None): - """ - Each app should have this __init__ to set up Redis and logging. - :param redis: - :param logger: - :param console_logger: - """ - super().__init__(redis, logger, console_logger) - - def router(self): - return "This action should be skipped" - - def base64_conversion(self, string, operation): - if operation == "encode": - encoded_bytes = base64.b64encode(string.encode("utf-8")) - encoded_string = str(encoded_bytes, "utf-8") - return encoded_string - - elif operation == "decode": - try: - decoded_bytes = base64.b64decode(string) - try: - decoded_bytes = str(decoded_bytes, "utf-8") - except: - pass - - return decoded_bytes - except Exception as e: - #return string.decode("utf-16") - - self.logger.info(f"[WARNING] Error in normal decoding: {e}") - return { - "success": False, - "reason": f"Error decoding the base64: {e}", - } - #newvar = binascii.a2b_base64(string) - #try: - # if str(newvar).startswith("b'") and str(newvar).endswith("'"): - # newvar = newvar[2:-1] - #except Exception as e: - # self.logger.info(f"Encoding issue in base64: {e}") - #return newvar - - #try: - # return newvar - #except: - # pass - - return { - "success": False, - "reason": "Error decoding the base64", - } - - return json.dumps({ - "success": False, - "reason": "No base64 to be converted", - }) - - def parse_list(self, input_list): - try: - input_list = json.loads(input_list) - if isinstance(input_list, list): - input_list = ",".join(input_list) - else: - return json.dumps(input_list) - except: - pass - - input_list = input_list.replace(", ", ",", -1) - return input_list - - # This is an SMS function of Shuffle - def send_sms_shuffle(self, apikey, phone_numbers, body): - phone_numbers = self.parse_list(phone_numbers) - - targets = [phone_numbers] - if ", " in phone_numbers: - targets = phone_numbers.split(", ") - elif "," in phone_numbers: - targets = phone_numbers.split(",") - - data = {"numbers": targets, "body": body} - - url = "https://shuffler.io/api/v1/functions/sendsms" - headers = {"Authorization": "Bearer %s" % apikey} - return requests.post(url, headers=headers, json=data).text - - # This is an email function of Shuffle - def send_email_shuffle(self, apikey, recipients, subject, body, attachments=""): - recipients = self.parse_list(recipients) - - - targets = [recipients] - if ", " in recipients: - targets = recipients.split(", ") - elif "," in recipients: - targets = recipients.split(",") - - data = { - "targets": targets, - "subject": subject, - "body": body, - "type": "alert", - } - - # Read the attachments - if attachments != None and len(attachments) > 0: - try: - attachments = parse_list(attachments, splitter=",") - files = [] - for item in attachments: - new_file = self.get_file(file_ids) - files.append(new_file) - - data["attachments"] = files - except Exception as e: - self.logger.info(f"Error in attachment parsing for email: {e}") - - - url = "https://shuffler.io/api/v1/functions/sendmail" - headers = {"Authorization": "Bearer %s" % apikey} - return requests.post(url, headers=headers, json=data).text - - def repeat_back_to_me(self, call): - return call - - # https://github.com/fhightower/ioc-finder - def parse_file_ioc(self, file_ids, input_type="all"): - def parse(data): - try: - iocs = find_iocs(str(data)) - newarray = [] - for key, value in iocs.items(): - if input_type != "all": - if key not in input_type: - continue - if len(value) > 0: - for item in value: - if isinstance(value, dict): - for subkey, subvalue in value.items(): - if len(subvalue) > 0: - for subitem in subvalue: - data = { - "data": subitem, - "data_type": "%s_%s" - % (key[:-1], subkey), - } - if data not in newarray: - newarray.append(data) - else: - data = {"data": item, "data_type": key[:-1]} - if data not in newarray: - newarray.append(data) - for item in newarray: - if "ip" in item["data_type"]: - item["data_type"] = "ip" - return {"success": True, "items": newarray} - except Exception as excp: - return {"success": False, "message": "{}".format(excp)} - - if input_type == "": - input_type = "all" - else: - input_type = input_type.split(",") - - try: - file_ids = eval(file_ids) # nosec - except SyntaxError: - file_ids = file_ids - except NameError: - file_ids = file_ids - - return_value = None - if type(file_ids) == str: - return_value = parse(self.get_file(file_ids)["data"]) - elif type(file_ids) == list and type(file_ids[0]) == str: - return_value = [ - parse(self.get_file(file_id)["data"]) for file_id in file_ids - ] - elif ( - type(file_ids) == list - and type(file_ids[0]) == list - and type(file_ids[0][0]) == str - ): - return_value = [ - [parse(self.get_file(file_id2)["data"]) for file_id2 in file_id] - for file_id in file_ids - ] - else: - return "Invalid input" - return return_value - - # https://github.com/fhightower/ioc-finder - def parse_ioc(self, input_string, input_type="all"): - input_string = str(input_string) - if input_type == "": - input_type = "all" - else: - input_type = input_type.split(",") - - iocs = find_iocs(input_string) - newarray = [] - for key, value in iocs.items(): - if input_type != "all": - if key not in input_type: - continue - - if len(value) > 0: - for item in value: - # If in here: attack techniques. Shouldn't be 3 levels so no - # recursion necessary - if isinstance(value, dict): - for subkey, subvalue in value.items(): - if len(subvalue) > 0: - for subitem in subvalue: - data = { - "data": subitem, - "data_type": "%s_%s" % (key[:-1], subkey), - } - if data not in newarray: - newarray.append(data) - else: - data = {"data": item, "data_type": key[:-1]} - if data not in newarray: - newarray.append(data) - - # Reformatting IP - for item in newarray: - if "ip" in item["data_type"]: - item["data_type"] = "ip" - try: - item["is_private_ip"] = ipaddress.ip_address(item["data"]).is_private - except: - self.logger.info("Error parsing %s" % item["data"]) - - try: - newarray = json.dumps(newarray) - except json.decoder.JSONDecodeError as e: - return "Failed to parse IOC's: %s" % e - - return newarray - - def parse_list(self, items, splitter="\n"): - if splitter == "": - splitter = "\n" - - splititems = items.split(splitter) - - return str(splititems) - - def get_length(self, item): - if item.startswith("[") and item.endswith("]"): - try: - item = item.replace("'", '"', -1) - item = json.loads(item) - except json.decoder.JSONDecodeError as e: - self.logger.info("Parse error: %s" % e) - - return str(len(item)) - - def set_json_key(self, json_object, key, value): - self.logger.info(f"OBJ: {json_object}\nKEY: {key}\nVAL: {value}") - if isinstance(json_object, str): - try: - json_object = json.loads(json_object) - except json.decoder.JSONDecodeError as e: - return { - "success": False, - "reason": "Item is not valid JSON" - } - - if isinstance(json_object, list): - if len(json_object) == 1: - json_object = json_object[0] - else: - return { - "success": False, - "reason": "Item is valid JSON, but can't handle lists. Use .#" - } - - if not isinstance(json_object, object): - return { - "success": False, - "reason": "Item is not valid JSON (2)" - } - - if isinstance(value, str): - try: - value = json.loads(value) - except json.decoder.JSONDecodeError as e: - pass - - # Handle JSON paths - if "." in key: - base_object = json.loads(json.dumps(json_object)) - #base_object.output.recipients.notificationEndpointIds = ... - - keys = key.split(".") - if len(keys) >= 1: - first_object = keys[0] - - # This is awful :) - buildstring = "base_object" - for subkey in keys: - buildstring += f"[\"{subkey}\"]" - - buildstring += f" = {value}" - self.logger.info("BUILD: %s" % buildstring) - - #output = - exec(buildstring) - json_object = base_object - #json_object[first_object] = base_object - else: - json_object[key] = value - - return json_object - - def delete_json_keys(self, json_object, keys): - keys = self.parse_list(keys) - - splitdata = [keys] - if ", " in keys: - splitdata = keys.split(", ") - elif "," in keys: - splitdata = keys.split(",") - - for key in splitdata: - key = key.strip() - try: - del json_object[key] - except: - self.logger.info(f"[ERROR] Key {key} doesn't exist") - - return json_object - - def translate_value(self, input_data, translate_from, translate_to, else_value=""): - splitdata = [translate_from] - if ", " in translate_from: - splitdata = translate_from.split(", ") - elif "," in translate_from: - splitdata = translate_from.split(",") - - if isinstance(input_data, list) or isinstance(input_data, dict): - input_data = json.dumps(input_data) - - to_return = input_data - if isinstance(input_data, str): - found = False - for item in splitdata: - item = item.strip() - if item in input_data: - input_data = input_data.replace(item, translate_to) - found = True - - if not found and len(else_value) > 0: - input_data = else_value - - if input_data.lower() == "false": - return False - elif input_data.lower() == "true": - return True - - return input_data - - def map_value(self, input_data, mapping, default_value=""): - if not isinstance(mapping, dict) and not isinstance(mapping, object): - try: - mapping = json.loads(mapping) - except json.decoder.JSONDecodeError as e: - return { - "success": False, - "reason": "Mapping is not valid JSON: %s" % e, - } - - for key, value in mapping.items(): - try: - input_data = input_data.replace(key, str(value), -1) - except: - self.logger.info(f"Failed mapping output data for key {key}") - - return input_data - - # Changed with 1.1.0 to run with different returns - def regex_capture_group(self, input_data, regex): - try: - returnvalues = { - "success": True, - } - - matches = re.findall(regex, input_data) - self.logger.info(f"{matches}") - for item in matches: - if isinstance(item, str): - name = "group_0" - try: - returnvalues[name].append(item) - except: - returnvalues[name] = [item] - - else: - for i in range(0, len(item)): - name = "group_%d" % i - try: - returnvalues[name].append(item[i]) - except: - returnvalues[name] = [item[i]] - - return returnvalues - except re.error as e: - return { - "success": False, - "reason": "Bad regex pattern: %s" % e, - } - - def regex_replace( - self, input_data, regex, replace_string="", ignore_case="False" - ): - - #self.logger.info("=" * 80) - #self.logger.info(f"Regex: {regex}") - #self.logger.info(f"replace_string: {replace_string}") - #self.logger.info("=" * 80) - - if ignore_case.lower().strip() == "true": - return re.sub(regex, replace_string, input_data, flags=re.IGNORECASE) - else: - return re.sub(regex, replace_string, input_data) - - def execute_python(self, code): - self.logger.info(f"Python code {len(code)} {code}. If uuid, we'll try to download and use the file.") - - if len(code) == 36 and "-" in code: - filedata = self.get_file(code) - if filedata["success"] == False: - return { - "success": False, - "message": f"Failed to get file for ID {code}", - } - - if ".py" not in filedata["filename"]: - return { - "success": False, - "message": f"Filename needs to contain .py", - } - - - # Write the code to a file - # 1. Take the data into a file - # 2. Subprocess execute file? - try: - f = StringIO() - with redirect_stdout(f): - exec(code) # nosec :( - - s = f.getvalue() - - #try: - # s = s.encode("utf-8") - #except Exception as e: - # self.logger.info(f"Failed utf-8 encoding response: {e}") - - try: - return { - "success": True, - "message": s.strip(), - } - except Exception as e: - return { - "success": True, - "message": s, - } - - except Exception as e: - return { - "success": False, - "message": f"exception: {e}", - } - - def execute_bash(self, code, shuffle_input): - process = subprocess.Popen( - code, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - shell=True, # nosec - ) - stdout = process.communicate() - item = "" - if len(stdout[0]) > 0: - self.logger.info("[DEBUG] Succesfully ran bash!") - item = stdout[0] - else: - self.logger.info(f"[ERROR] FAILED to run bash command {code}!") - item = stdout[1] - - try: - ret = item.decode("utf-8") - return ret - except Exception: - return item - - return item - - def filter_list(self, input_list, field, check, value, opposite): - self.logger.info(f"\nRunning function with list {input_list}") - - flip = False - if str(opposite).lower() == "true": - flip = True - - try: - #input_list = eval(input_list) # nosec - input_list = json.loads(input_list) - except Exception: - try: - input_list = input_list.replace("'", '"', -1) - input_list = json.loads(input_list) - except Exception: - self.logger.info("[WARNING] Error parsing string to array. Continuing anyway.") - - # Workaround D: - if not isinstance(input_list, list): - return { - "success": False, - "reason": "Error: input isnt a list. Remove # to use this action.", - "valid": [], - "invalid": [], - } - - input_list = [input_list] - - self.logger.info(f"\nRunning with check \"%s\" on list of length %d\n" % (check, len(input_list))) - found_items = [] - new_list = [] - failed_list = [] - for item in input_list: - try: - try: - item = json.loads(item) - except Exception: - pass - - # Support for nested dict key - tmp = item - if field and field.strip() != "": - for subfield in field.split("."): - tmp = tmp[subfield] - - if isinstance(tmp, dict) or isinstance(tmp, list): - try: - tmp = json.dumps(tmp) - except json.decoder.JSONDecodeError as e: - self.logger.info("FAILED DECODING: %s" % e) - pass - - #self.logger.info("PRE CHECKS FOR TMP: %") - - # EQUALS JUST FOR STR - if check == "equals": - # Mostly for bools - # value = tmp.lower() - - if str(tmp).lower() == str(value).lower(): - self.logger.info("APPENDED BECAUSE %s %s %s" % (field, check, value)) - if not flip: - new_list.append(item) - else: - failed_list.append(item) - else: - if flip: - new_list.append(item) - else: - failed_list.append(item) - - # IS EMPTY FOR STR OR LISTS - elif check == "is empty": - if tmp == "[]": - tmp = [] - - if type(tmp) == list and len(tmp) == 0 and not flip: - new_list.append(item) - elif type(tmp) == list and len(tmp) > 0 and flip: - new_list.append(item) - elif type(tmp) == str and not tmp and not flip: - new_list.append(item) - elif type(tmp) == str and tmp and flip: - new_list.append(item) - else: - failed_list.append(item) - - # STARTS WITH = FOR STR OR [0] FOR LIST - elif check == "starts with": - if type(tmp) == list and tmp[0] == value and not flip: - new_list.append(item) - elif type(tmp) == list and tmp[0] != value and flip: - new_list.append(item) - elif type(tmp) == str and tmp.startswith(value) and not flip: - new_list.append(item) - elif type(tmp) == str and not tmp.startswith(value) and flip: - new_list.append(item) - else: - failed_list.append(item) - - # ENDS WITH = FOR STR OR [-1] FOR LIST - elif check == "ends with": - if type(tmp) == list and tmp[-1] == value and not flip: - new_list.append(item) - elif type(tmp) == list and tmp[-1] != value and flip: - new_list.append(item) - elif type(tmp) == str and tmp.endswith(value) and not flip: - new_list.append(item) - elif type(tmp) == str and not tmp.endswith(value) and flip: - new_list.append(item) - else: - failed_list.append(item) - - # CONTAINS FIND FOR LIST AND IN FOR STR - elif check == "contains": - if type(tmp) == list and value.lower() in tmp and not flip: - new_list.append(item) - elif type(tmp) == list and value.lower() not in tmp and flip: - new_list.append(item) - elif ( - type(tmp) == str - and tmp.lower().find(value.lower()) != -1 - and not flip - ): - new_list.append(item) - elif ( - type(tmp) == str - and tmp.lower().find(value.lower()) == -1 - and flip - ): - new_list.append(item) - else: - failed_list.append(item) - elif check == "contains any of": - self.logger.info("Inside contains any of") - checklist = value.split(",") - self.logger.info("Checklist and tmp: %s - %s" % (checklist, tmp)) - found = False - for subcheck in checklist: - subcheck = subcheck.strip().lower() - #ext.lower().strip() == value.lower().strip() - if type(tmp) == list and subcheck in tmp and not flip: - new_list.append(item) - found = True - break - elif type(tmp) == list and subcheck in tmp and flip: - failed_list.append(item) - found = True - break - elif type(tmp) == list and subcheck not in tmp and not flip: - new_list.append(item) - found = True - break - elif type(tmp) == list and subcheck not in tmp and flip: - failed_list.append(item) - found = True - break - elif (type(tmp) == str and tmp.lower().find(subcheck) != -1 and not flip): - new_list.append(item) - found = True - break - elif (type(tmp) == str and tmp.lower().find(subcheck) != -1 and flip): - failed_list.append(item) - found = True - break - elif (type(tmp) == str and tmp.lower().find(subcheck) == -1 and not flip): - failed_list.append(item) - found = True - break - elif (type(tmp) == str and tmp.lower().find(subcheck) == -1 and flip): - new_list.append(item) - found = True - break - - if not found: - failed_list.append(item) - - # CONTAINS FIND FOR LIST AND IN FOR STR - elif check == "field is unique": - #self.logger.info("FOUND: %s" - if tmp.lower() not in found_items and not flip: - new_list.append(item) - found_items.append(tmp.lower()) - elif tmp.lower() in found_items and flip: - new_list.append(item) - found_items.append(tmp.lower()) - else: - failed_list.append(item) - - #tmp = json.dumps(tmp) - - #for item in new_list: - #if type(tmp) == list and value.lower() in tmp and not flip: - # new_list.append(item) - # found = True - # break - #elif type(tmp) == list and value.lower() not in tmp and flip: - # new_list.append(item) - # found = True - # break - - # CONTAINS FIND FOR LIST AND IN FOR STR - elif check == "contains any of": - value = self.parse_list(value) - checklist = value.split(",") - tmp = tmp.lower() - self.logger.info("CHECKLIST: %s. Value: %s" % (checklist, tmp)) - found = False - for value in checklist: - if value in tmp and not flip: - new_list.append(item) - found = True - break - elif value not in tmp and flip: - new_list.append(item) - found = True - break - - if not found: - failed_list.append(item) - - elif check == "larger than": - if int(tmp) > int(value) and not flip: - new_list.append(item) - elif int(tmp) > int(value) and flip: - new_list.append(item) - else: - failed_list.append(item) - elif check == "less than": - if int(tmp) < int(value) and not flip: - new_list.append(item) - elif int(tmp) < int(value) and flip: - new_list.append(item) - else: - failed_list.append(item) - - # SINGLE ITEM COULD BE A FILE OR A LIST OF FILES - elif check == "files by extension": - if type(tmp) == list: - file_list = [] - - for file_id in tmp: - filedata = self.get_file(file_id) - _, ext = os.path.splitext(filedata["filename"]) - if ( - ext.lower().strip() == value.lower().strip() - and not flip - ): - file_list.append(file_id) - elif ext.lower().strip() != value.lower().strip() and flip: - file_list.append(file_id) - # else: - # failed_list.append(file_id) - - tmp = item - if field and field.strip() != "": - for subfield in field.split(".")[:-1]: - tmp = tmp[subfield] - tmp[field.split(".")[-1]] = file_list - new_list.append(item) - else: - new_list = file_list - # else: - # failed_list = file_list - - elif type(tmp) == str: - filedata = self.get_file(tmp) - _, ext = os.path.splitext(filedata["filename"]) - if ext.lower().strip() == value.lower().strip() and not flip: - new_list.append(item) - elif ext.lower().strip() != value.lower().strip() and flip: - new_list.append((item, ext)) - else: - failed_list.append(item) - - except Exception as e: - # "Error: %s" % e - self.logger.info("[WARNING] FAILED WITH EXCEPTION: %s" % e) - failed_list.append(item) - # return - - try: - return json.dumps( - { - "success": True, - "valid": new_list, - "invalid": failed_list, - } - ) - # new_list = json.dumps(new_list) - except json.decoder.JSONDecodeError as e: - return json.dumps( - { - "success": False, - "reason": "Failed parsing filter list output" + e, - } - ) - - return new_list - - #def multi_list_filter(self, input_list, field, check, value): - # input_list = input_list.replace("'", '"', -1) - # input_list = json.loads(input_list) - - # fieldsplit = field.split(",") - # if ", " in field: - # fieldsplit = field.split(", ") - - # valuesplit = value.split(",") - # if ", " in value: - # valuesplit = value.split(", ") - - # checksplit = check.split(",") - # if ", " in check: - # checksplit = check.split(", ") - - # new_list = [] - # for list_item in input_list: - # list_item = json.loads(list_item) - - # index = 0 - # for check in checksplit: - # if check == "equals": - # self.logger.info( - # "Checking %s vs %s" - # % (list_item[fieldsplit[index]], valuesplit[index]) - # ) - # if list_item[fieldsplit[index]] == valuesplit[index]: - # new_list.append(list_item) - - # index += 1 - - # # "=", - # # "equals", - # # "!=", - # # "does not equal", - # # ">", - # # "larger than", - # # "<", - # # "less than", - # # ">=", - # # "<=", - # # "startswith", - # # "endswith", - # # "contains", - # # "re", - # # "matches regex", - - # try: - # new_list = json.dumps(new_list) - # except json.decoder.JSONDecodeError as e: - # return "Failed parsing filter list output" % e - - # return new_list - - # Gets the file's metadata, e.g. md5 - def get_file_meta(self, file_id): - headers = { - "Authorization": "Bearer %s" % self.authorization, - } - - ret = requests.get( - "%s/api/v1/files/%s?execution_id=%s" - % (self.url, file_id, self.current_execution_id), - headers=headers, - ) - self.logger.info(f"RET: {ret}") - - return ret.text - - # Use data from AppBase to talk to backend - def delete_file(self, file_id): - headers = { - "Authorization": "Bearer %s" % self.authorization, - } - self.logger.info("HEADERS: %s" % headers) - - ret = requests.delete( - "%s/api/v1/files/%s?execution_id=%s" - % (self.url, file_id, self.current_execution_id), - headers=headers, - ) - return ret.text - - def create_file(self, filename, data): - self.logger.info("Inside function") - - #try: - # if str(data).startswith("b'") and str(data).endswith("'"): - # data = data[2:-1] - #except Exception as e: - # self.logger.info(f"Exception: {e}") - - filedata = { - "filename": filename, - "data": data, - } - - fileret = self.set_files([filedata]) - value = {"success": True, "file_ids": fileret} - if len(fileret) == 1: - value = {"success": True, "file_ids": fileret[0]} - - return value - - # Input is WAS a file, hence it didn't get the files - def get_file_value(self, filedata): - filedata = self.get_file(filedata) - if filedata is None: - return "File is empty?" - - self.logger.info("INSIDE APP DATA: %s" % filedata) - try: - return filedata["data"].decode() - except: - try: - return filedata["data"].decode("utf-16") - except: - return { - "success": False, - "reason": "Got the file, but the encoding can't be printed", - } - - def download_remote_file(self, url): - ret = requests.get(url, verify=False) # nosec - filename = url.split("/")[-1] - fileret = self.set_files( - [ - { - "filename": filename, - "data": ret.content, - } - ] - ) - - if len(fileret) > 0: - value = {"success": True, "file_id": fileret[0]} - else: - value = {"success": False, "reason": "No files downloaded"} - - return value - - def extract_archive(self, file_ids, fileformat="zip", password=None): - try: - return_data = {"success": False, "files": []} - - try: - file_ids = eval(file_ids) # nosec - except SyntaxError: - file_ids = file_ids - - self.logger.info("IDS: %s" % file_ids) - items = file_ids if type(file_ids) == list else file_ids.split(",") - for file_id in items: - - item = self.get_file(file_id) - return_ids = None - - self.logger.info("Working with fileformat %s" % fileformat) - with tempfile.TemporaryDirectory() as tmpdirname: - - # Get archive and save phisically - with open(os.path.join(tmpdirname, "archive"), "wb") as f: - f.write(item["data"]) - - # Grab files before, upload them later - to_be_uploaded = [] - - # Zipfile for zipped archive - if fileformat.strip().lower() == "zip": - try: - with zipfile.ZipFile( - os.path.join(tmpdirname, "archive") - ) as z_file: - if password: - z_file.setpassword(bytes(password.encode())) - for member in z_file.namelist(): - filename = os.path.basename(member) - if not filename: - continue - source = z_file.open(member) - to_be_uploaded.append( - {"filename": source.name, "data": source.read()} - ) - return_data["success"] = True - except (zipfile.BadZipFile, Exception): - return_data["files"].append( - { - "success": False, - "file_id": file_id, - "filename": item["filename"], - "message": "File is not a valid zip archive", - } - ) - - continue - - elif fileformat.strip().lower() == "rar": - try: - with rarfile.RarFile( - os.path.join(tmpdirname, "archive") - ) as z_file: - if password: - z_file.setpassword(password) - for member in z_file.namelist(): - filename = os.path.basename(member) - if not filename: - continue - source = z_file.open(member) - to_be_uploaded.append( - {"filename": source.name, "data": source.read()} - ) - return_data["success"] = True - except Exception: - return_data["files"].append( - { - "success": False, - "file_id": file_id, - "filename": item["filename"], - "message": "File is not a valid rar archive", - } - ) - continue - - elif fileformat.strip().lower() == "tar": - try: - with tarfile.open( - os.path.join(tmpdirname, "archive"), mode="r" - ) as z_file: - for member in z_file.getnames(): - member_files = z_file.extractfile(member) - to_be_uploaded.append( - { - "filename": member, - "data": member_files.read(), - } - ) - return_data["success"] = True - except Exception as e: - return_data["files"].append( - { - "success": False, - "file_id": file_id, - "filename": item["filename"], - "message": e, - } - ) - continue - elif fileformat.strip().lower() == "tar.gz": - try: - with tarfile.open( - os.path.join(tmpdirname, "archive"), mode="r:gz" - ) as z_file: - for member in z_file.getnames(): - member_files = z_file.extractfile(member) - to_be_uploaded.append( - { - "filename": member, - "data": member_files.read(), - } - ) - return_data["success"] = True - except Exception as e: - return_data["files"].append( - { - "success": False, - "file_id": file_id, - "filename": item["filename"], - "message": e, - } - ) - continue - - elif fileformat.strip().lower() == "7zip": - try: - with py7zr.SevenZipFile( - os.path.join(tmpdirname, "archive"), - mode="r", - password=password if password else None, - ) as z_file: - for filename, source in z_file.readall().items(): - # Removes paths - filename = filename.split("/")[-1] - to_be_uploaded.append( - { - "filename": item["filename"], - "data": source.read(), - } - ) - return_data["success"] = True - except Exception: - return_data["files"].append( - { - "success": False, - "file_id": file_id, - "filename": item["filename"], - "message": "File is not a valid 7zip archive", - } - ) - continue - else: - return "No such format: %s" % fileformat - - if len(to_be_uploaded) > 0: - return_ids = self.set_files(to_be_uploaded) - return_data["files"].append( - { - "success": True, - "file_id": file_id, - "filename": item["filename"], - "file_ids": return_ids, - } - ) - else: - return_data["files"].append( - { - "success": False, - "file_id": file_id, - "filename": item["filename"], - "message": "Archive is empty", - } - ) - - return return_data - - except Exception as excp: - return {"success": False, "message": "%s" % excp} - - def inflate_archive(self, file_ids, fileformat, name, password=None): - - try: - # TODO: will in future support multiple files instead of string ids? - file_ids = file_ids.split() - self.logger.info("picking {}".format(file_ids)) - - # GET all items from shuffle - items = [self.get_file(file_id) for file_id in file_ids] - - if len(items) == 0: - return "No file to inflate" - - # Dump files on disk, because libs want path :( - with tempfile.TemporaryDirectory() as tmpdir: - paths = [] - self.logger.info("Number 1") - for item in items: - with open(os.path.join(tmpdir, item["filename"]), "wb") as f: - f.write(item["data"]) - paths.append(os.path.join(tmpdir, item["filename"])) - - # Create archive temporary - self.logger.info("{} items to inflate".format(len(items))) - with tempfile.NamedTemporaryFile() as archive: - - if fileformat == "zip": - archive_name = "archive.zip" if not name else name - pyminizip.compress_multiple( - paths, [], archive.name, password, 5 - ) - - elif fileformat == "7zip": - archive_name = "archive.7z" if not name else name - with py7zr.SevenZipFile( - archive.name, - "w", - password=password if len(password) > 0 else None, - ) as sz_archive: - for path in paths: - sz_archive.write(path) - - else: - return "Format {} not supported".format(fileformat) - - return_id = self.set_files( - [{"filename": archive_name, "data": open(archive.name, "rb")}] - ) - - if len(return_id) == 1: - # Returns the first file's ID - return {"success": True, "id": return_id[0]} - else: - return { - "success": False, - "message": "Upload archive returned {}".format(return_id), - } - - except Exception as excp: - return {"success": False, "message": excp} - - def add_list_to_list(self, list_one, list_two): - if isinstance(list_one, str): - if not list_one or list_one == " " or list_one == "None" or list_one == "null": - list_one = "[]" - - try: - list_one = json.loads(list_one) - except json.decoder.JSONDecodeError as e: - self.logger.info("Failed to parse list1 as json: %s" % e) - if list_one == None: - list_one = [] - else: - return { - "success": False, - "reason": f"List one is not a valid list: {list_one}" - } - - if isinstance(list_two, str): - if not list_two or list_two == " " or list_two == "None" or list_two == "null": - list_two = "[]" - try: - list_two = json.loads(list_two) - except json.decoder.JSONDecodeError as e: - self.logger.info("Failed to parse list2 as json: %s" % e) - if list_one == None: - list_one = [] - else: - return { - "success": False, - "reason": f"List two is not a valid list: {list_two}" - } - - if isinstance(list_one, dict): - list_one = [list_one] - if isinstance(list_two, dict): - list_two = [list_two] - - for item in list_two: - list_one.append(item) - - return list_one - - def diff_lists(self, list_one, list_two): - if isinstance(list_one, str): - try: - list_one = json.loads(list_one) - except json.decoder.JSONDecodeError as e: - self.logger.info("Failed to parse list1 as json: %s" % e) - - if isinstance(list_two, str): - try: - list_two = json.loads(list_two) - except json.decoder.JSONDecodeError as e: - self.logger.info("Failed to parse list2 as json: %s" % e) - - def diff(li1, li2): - return list(set(li1) - set(li2)) + list(set(li2) - set(li1)) - - return diff(list_one, list_two) - - def merge_lists(self, list_one, list_two, set_field="", sort_key_list_one="", sort_key_list_two=""): - if isinstance(list_one, str): - try: - list_one = json.loads(list_one) - except json.decoder.JSONDecodeError as e: - self.logger.info("Failed to parse list1 as json: %s" % e) - - if isinstance(list_two, str): - try: - list_two = json.loads(list_two) - except json.decoder.JSONDecodeError as e: - self.logger.info("Failed to parse list2 as json: %s" % e) - - if len(list_one) != len(list_two): - return {"success": False, "message": "Lists length must be the same. %d vs %d" % (len(list_one), len(list_two))} - - if len(sort_key_list_one) > 0: - self.logger.info("Sort 1 %s by key: %s" % (list_one, sort_key_list_one)) - try: - list_one = sorted(list_one, key=lambda k: k.get(sort_key_list_one), reverse=True) - except: - self.logger.info("Failed to sort list one") - pass - - if len(sort_key_list_two) > 0: - #self.logger.info("Sort 2 %s by key: %s" % (list_two, sort_key_list_two)) - try: - list_two = sorted(list_two, key=lambda k: k.get(sort_key_list_two), reverse=True) - except: - self.logger.info("Failed to sort list one") - pass - - # Loops for each item in sub array and merges items together - # List one is being overwritten - base_key = "shuffle_auto_merge" - try: - for i in range(len(list_one)): - #self.logger.info(list_two[i]) - if isinstance(list_two[i], dict): - for key, value in list_two[i].items(): - list_one[i][key] = value - elif isinstance(list_two[i], str) and list_two[i] == "": - continue - elif isinstance(list_two[i], str) or isinstance(list_two[i], int) or isinstance(list_two[i], bool): - self.logger.info("IN SETTER FOR %s" % list_two[i]) - if len(set_field) == 0: - self.logger.info("Define a JSON key to set for List two (Set Field)") - list_one[i][base_key] = list_two[i] - else: - list_one[i][set_field] = list_two[i] - except Exception as e: - return { - "success": False, - "reason": "An error occurred while merging the lists. PS: List one can NOT be a list of integers. If this persists, contact us at support@shuffler.io", - "exception": f"{e}", - } - - - return list_one - - def xml_json_convertor(self, convertto, data): - try: - if convertto == "json": - ans = xmltodict.parse(data) - json_data = json.dumps(ans) - return json_data - else: - ans = readfromstring(data) - return json2xml.Json2xml(ans, wrapper="all", pretty=True).to_xml() - except Exception as e: - return e - - def date_to_epoch(self, input_data, date_field, date_format): - - self.logger.info( - "Executing with {} on {} with format {}".format( - input_data, date_field, date_format - ) - ) - - result = json.loads(input_data) - #try: - #except json.decoder.JSONDecodeError as e: - # result = input_data - - # https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior - epoch = datetime.datetime.strptime(result[date_field], date_format).strftime( - "%s" - ) - result["epoch"] = epoch - return result - - def compare_relative_date( - self, input_data, date_format, equality_test, offset, units, direction - ): - - if input_data == "None": - return False - - self.logger.info("Converting input date.") - - if date_format != "%s": - input_dt = datetime.datetime.strptime(input_data, date_format) - else: - input_dt = datetime.datetime.utcfromtimestamp(float(input_data)) - - offset = int(offset) - if units == "seconds": - delta = datetime.timedelta(seconds=offset) - elif units == "minutes": - delta = datetime.timedelta(minutes=offset) - elif units == "hours": - delta = datetime.timedelta(hours=offset) - elif units == "days": - delta = datetime.timedelta(days=offset) - - utc_format = date_format - if utc_format.endswith("%z"): - utc_format = utc_format.replace("%z", "Z") - - if date_format != "%s": - formatted_dt = datetime.datetime.strptime( - datetime.datetime.utcnow().strftime(utc_format), date_format - ) - else: - formatted_dt = datetime.datetime.utcnow() - - self.logger.info("Formatted time is: {}".format(formatted_dt)) - if direction == "ago": - comparison_dt = formatted_dt - delta - else: - comparison_dt = formatted_dt + delta - self.logger.info("{} {} {} is {}".format(offset, units, direction, comparison_dt)) - - diff = (input_dt - comparison_dt).total_seconds() - self.logger.info( - "Difference between {} and {} is {}".format(input_data, comparison_dt, diff) - ) - result = False - if equality_test == ">": - result = 0 > diff - if direction == "ahead": - result = not (result) - elif equality_test == "<": - result = 0 < diff - if direction == "ahead": - result = not (result) - elif equality_test == "=": - result = diff == 0 - elif equality_test == "!=": - result = diff != 0 - elif equality_test == ">=": - result = 0 >= diff - if direction == "ahead" and diff != 0: - result = not (result) - elif equality_test == "<=": - result = 0 <= diff - if direction == "ahead" and diff != 0: - result = not (result) - - self.logger.info( - "At {}, is {} {} than {} {} {}? {}".format( - formatted_dt, - input_data, - equality_test, - offset, - units, - direction, - result, - ) - ) - - return result - - def run_math_operation(self, operation): - self.logger.info("Operation: %s" % operation) - result = eval(operation) - return result - - def escape_html(self, input_data, field_name): - - mapping = json.loads(input_data) - self.logger.info(f"Got mapping {json.dumps(mapping, indent=2)}") - - result = markupsafe.escape(mapping[field_name]) - self.logger.info(f"Mapping {input_data} to {result}") - - mapping[field_name] = result - return mapping - - def check_cache_contains(self, key, value, append): - org_id = self.full_execution["workflow"]["execution_org"]["id"] - url = "%s/api/v1/orgs/%s/get_cache" % (self.url, org_id) - data = { - "workflow_id": self.full_execution["workflow"]["id"], - "execution_id": self.current_execution_id, - "authorization": self.authorization, - "org_id": org_id, - "key": key, - } - - if append.lower() == "true": - append = True - else: - append = False - - get_response = requests.post(url, json=data) - try: - allvalues = get_response.json() - try: - if allvalues["value"] == None or allvalues["value"] == "null": - allvalues["value"] = "[]" - except: - pass - - if allvalues["success"] == False: - if append == True: - new_value = [str(value)] - data["value"] = json.dumps(new_value) - - set_url = "%s/api/v1/orgs/%s/set_cache" % (self.url, org_id) - set_response = requests.post(set_url, json=data) - try: - allvalues = set_response.json() - #allvalues["key"] = key - #return allvalues - - return { - "success": True, - "found": False, - "key": key, - "value": new_value, - } - except Exception as e: - return { - "success": False, - "found": False, - "key": key, - "reason": "Failed to find key, and failed to append", - } - else: - return { - "success": True, - "found": False, - "key": key, - "reason": "Not appended, not found", - } - else: - if allvalues["value"] == None or allvalues["value"] == "null": - allvalues["value"] = "[]" - - try: - parsedvalue = json.loads(allvalues["value"]) - except json.decoder.JSONDecodeError as e: - parsedvalue = [] - - #return parsedvalue - - for item in parsedvalue: - #return "%s %s" % (item, value) - if item == value: - if not append: - return { - "success": True, - "found": True, - "reason": "Found and not appending!", - "key": key, - "value": json.loads(allvalues["value"]), - } - else: - return { - "success": True, - "found": True, - "reason": "Found, was appending, but item already exists", - "key": key, - "value": json.loads(allvalues["value"]), - } - - # Lol - break - - if not append: - return { - "success": True, - "found": False, - "reason": "Not found, not appending (2)!", - "key": key, - "value": json.loads(allvalues["value"]), - } - - #parsedvalue = json.loads(allvalues["value"]) - #if parsedvalue == None: - # parsedvalue = [] - - #return parsedvalue - new_value = parsedvalue - if new_value == None: - new_value = [value] - - new_value.append(value) - - #return new_value - - data["value"] = json.dumps(new_value) - #return allvalues - - set_url = "%s/api/v1/orgs/%s/set_cache" % (self.url, org_id) - response = requests.post(set_url, json=data) - exception = "" - try: - allvalues = response.json() - #return allvalues - - return { - "success": True, - "found": False, - "reason": "Appended as it didn't exist", - "key": key, - "value": new_value, - } - except Exception as e: - exception = e - pass - - return { - "success": False, - "found": True, - "reason": f"Failed to set append the value: {exception}. This should never happen", - "key": key - } - - self.logger.info("Handle all values!") - - #return allvalues - - except Exception as e: - return { - "success": False, - "key": key, - "reason": f"Failed to get cache: {e}", - "found": False, - } - - return value.text - - def get_cache_value(self, key): - org_id = self.full_execution["workflow"]["execution_org"]["id"] - url = "%s/api/v1/orgs/%s/get_cache" % (self.url, org_id) - data = { - "workflow_id": self.full_execution["workflow"]["id"], - "execution_id": self.current_execution_id, - "authorization": self.authorization, - "org_id": org_id, - "key": key, - } - - value = requests.post(url, json=data) - try: - allvalues = value.json() - self.logger.info("VAL1: ", allvalues) - allvalues["key"] = key - self.logger.info("VAL2: ", allvalues) - - if allvalues["success"] == True: - allvalues["found"] = True - else: - allvalues["success"] = True - allvalues["found"] = False - - try: - parsedvalue = json.loads(allvalues["value"]) - allvalues["value"] = parsedvalue - - except: - self.logger.info("Parsing of value as JSON failed") - pass - - return json.dumps(allvalues) - except: - self.logger.info("Value couldn't be parsed, or json dump of value failed") - return value.text - - # FIXME: Add option for org only & sensitive data (not to be listed) - def set_cache_value(self, key, value): - org_id = self.full_execution["workflow"]["execution_org"]["id"] - url = "%s/api/v1/orgs/%s/set_cache" % (self.url, org_id) - data = { - "workflow_id": self.full_execution["workflow"]["id"], - "execution_id": self.current_execution_id, - "authorization": self.authorization, - "org_id": org_id, - "key": key, - "value": str(value), - } - - response = requests.post(url, json=data) - try: - allvalues = response.json() - allvalues["key"] = key - #allvalues["value"] = json.loads(json.dumps(value)) - - if (value.startswith("{") and value.endswith("}")) or (value.startswith("[") and value.endswith("]")): - try: - allvalues["value"] = json.loads(value) - except json.decoder.JSONDecodeError as e: - self.logger.info("Failed inner value parsing: %s" % e) - allvalues["value"] = str(value) - else: - allvalues["value"] = str(value) - - return json.dumps(allvalues) - except: - self.logger.info("Value couldn't be parsed") - return response.text - - def convert_json_to_tags(self, json_object, split_value=", ", include_key=True, lowercase=True): - try: - json_object = json.loads(json_object) - except json.decoder.JSONDecodeError as e: - self.logger.info("Failed to parse list2 as json: %s. Type: %s" % (e, type(json_object))) - - if isinstance(lowercase, str) and lowercase.lower() == "true": - lowercase = True - else: - lowercase = False - - if isinstance(include_key, str) or include_key.lower() == "true": - include_key = True - else: - include_key = False - - parsedstring = [] - try: - for key, value in json_object.items(): - self.logger.info("KV: %s:%s" % (key, value)) - if isinstance(value, str) or isinstance(value, int) or isinstance(value, bool): - if include_key == True: - parsedstring.append("%s:%s" % (key, value)) - else: - parsedstring.append("%s" % (value)) - else: - self.logger.info("Can't handle type %s" % type(value)) - except AttributeError as e: - return { - "success": False, - "reason": "Json Object is not a dictionary", - } - - fullstring = split_value.join(parsedstring) - if lowercase == True: - fullstring = fullstring.lower() - - return fullstring - - def cidr_ip_match(self, ip, networks): - self.logger.info("Executing with\nIP: {},\nNetworks: {}".format(ip, networks)) - - try: - networks = json.loads(networks) - except json.decoder.JSONDecodeError as e: - self.logger.info("Failed to parse networks list as json: {}. Type: {}".format( - e, type(networks) - )) - return "Networks is not a valid list: {}".format(networks) - - try: - ip_networks = list(map(ipaddress.ip_network, networks)) - ip_address = ipaddress.ip_address(ip) - except ValueError as e: - return "IP or some networks are not in valid format.\nError: {}".format(e) - - matched_networks = list(filter(lambda net: (ip_address in net), ip_networks)) - - result = {} - result['networks'] = list(map(str, matched_networks)) - result['is_contained'] = True if len(result['networks']) > 0 else False - - return json.dumps(result) - - def get_timestamp(self, time_format): - timestamp = int(time.time()) - if time_format == "unix" or time_format == "epoch": - self.logger.info("Running default timestamp %s" % timestamp) - - return timestamp - - def get_hash_sum(self, value): - md5_value = "" - sha256_value = "" - - try: - md5_value = hashlib.md5(str(value).encode('utf-8')).hexdigest() - except Exception as e: - self.logger.info(f"Error in md5sum: {e}") - - try: - sha256_value = hashlib.sha256(str(value).encode('utf-8')).hexdigest() - except Exception as e: - self.logger.info(f"Error in sha256: {e}") - - parsedvalue = { - "success": True, - "original_value": value, - "md5": md5_value, - "sha256": sha256_value, - } - - return parsedvalue - -if __name__ == "__main__": - Tools.run() From c75a7a1abf09456b5a4d75ee6905864dcfd3cddb Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 28 Aug 2025 16:57:41 +0200 Subject: [PATCH 236/259] Optimised active directory --- active-directory/1.0.0/requirements.txt | 2 +- active-directory/1.0.0/src/app.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/active-directory/1.0.0/requirements.txt b/active-directory/1.0.0/requirements.txt index 53f3e6a1..2c477e61 100644 --- a/active-directory/1.0.0/requirements.txt +++ b/active-directory/1.0.0/requirements.txt @@ -1,2 +1,2 @@ ldap3==2.9.1 -requests==2.32.4 +shuffle-sdk==0.0.28 diff --git a/active-directory/1.0.0/src/app.py b/active-directory/1.0.0/src/app.py index e9a3558f..d7091b93 100644 --- a/active-directory/1.0.0/src/app.py +++ b/active-directory/1.0.0/src/app.py @@ -11,7 +11,7 @@ from ldap3.extend.microsoft.addMembersToGroups import ad_add_members_to_groups as addUsersInGroups from ldap3.extend.microsoft.removeMembersFromGroups import ad_remove_members_from_groups as removeUsersFromGroups -from walkoff_app_sdk.app_base import AppBase +from shuffle_sdk import AppBase class ActiveDirectory(AppBase): __version__ = "1.0.1" @@ -464,15 +464,15 @@ def remove_user_from_group(self, server, domain, port, login_user, password, bas c.search(base_dn, f"(SAMAccountName={samaccountname})") if len(c.entries) == 0: return {"success":"false","message":f"User {samaccountname} not found"} - user_dn = c.entries[0].entry_dn + user_dn = c.entries[0].entry_dn search_filter = f'(&(objectClass=group)(cn={group_name}))' c.search(base_dn, search_filter, attributes=["distinguishedName"]) if len(c.entries) == 0: return {"success":"false","message":f"Group {group_name} not found"} + group_dn = c.entries[0]["distinguishedName"] print(group_dn) - res = removeUsersFromGroups(c, user_dn, str(group_dn),fix=True) if res == True: return {"success":"true","message":f"User {samaccountname} was removed from group {group_name}"} From 43374726ba967900bdd1b90b98782893ae2db993 Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 1 Sep 2025 18:40:49 +0200 Subject: [PATCH 237/259] Fixed an issue with types on search_datastore_category --- shuffle-tools/1.2.0/src/app.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 3dce6b0f..c99aa240 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -2227,6 +2227,7 @@ def search_datastore_category(self, input_list, key, category): url = f"{self.url}/api/v2/datastore?bulk=true&execution_id={self.current_execution_id}&authorization={self.authorization}" response = requests.post(url, json=data, verify=False) + if response.status_code != 200: returnvalue["reason"] = "Failed to check datastore key exists" returnvalue["details"] = response.text @@ -2240,16 +2241,20 @@ def search_datastore_category(self, input_list, key, category): return response.text if "keys_existed" not in data: - return data + returnvalue["error"] = "Invalid response from backend during bulk update of keys" + returnvalue["details"] = data + + return returnvalue + not_found_keys = [] returnvalue["success"] = True for datastore_item in input_list: found = False for existing_item in data["keys_existed"]: - if existing_item["key"] != datastore_item[key]: + if str(existing_item["key"]) != str(datastore_item[key]): continue - if existing_item["existed"]: + if existing_item["existed"] == True: returnvalue["exists"].append(datastore_item) else: returnvalue["new"].append(datastore_item) @@ -2259,7 +2264,12 @@ def search_datastore_category(self, input_list, key, category): if not found: print("[ERROR][%s] Key %s not found in datastore response, adding as new" % (self.current_execution_id, datastore_item[key])) - returnvalue["new"].append(datastore_item) + #returnvalue["new"].append(datastore_item) + not_found_keys.append(datastore_item[key]) + + if len(not_found_keys) > 0: + returnvalue["unhandled_keys"] = not_found_keys + returnvalue["reason"] = "Something went wrong updating the unhandled_keys. Please contact support@shuffler.io if this persists." return json.dumps(returnvalue, indent=4) From d15b800f443c941cd63ecb11248804e50639e417 Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 19 Sep 2025 15:13:18 +0200 Subject: [PATCH 238/259] Rebuild subflow with new app sdk --- shuffle-subflow/1.1.0/requirements.txt | 3 ++- shuffle-subflow/1.1.0/src/app.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/shuffle-subflow/1.1.0/requirements.txt b/shuffle-subflow/1.1.0/requirements.txt index 480d0c4b..41daa615 100644 --- a/shuffle-subflow/1.1.0/requirements.txt +++ b/shuffle-subflow/1.1.0/requirements.txt @@ -1 +1,2 @@ -requests==2.32.4 \ No newline at end of file +requests==2.32.4 +shuffle-sdk diff --git a/shuffle-subflow/1.1.0/src/app.py b/shuffle-subflow/1.1.0/src/app.py index 252e46e9..f51ace59 100644 --- a/shuffle-subflow/1.1.0/src/app.py +++ b/shuffle-subflow/1.1.0/src/app.py @@ -1,7 +1,7 @@ import json import requests -from walkoff_app_sdk.app_base import AppBase +from shuffle_sdk import AppBase class Subflow(AppBase): """ From f353dbea161208799dab5600acbd9c3c4be354d1 Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 19 Sep 2025 15:14:30 +0200 Subject: [PATCH 239/259] Changed AppBase --- shuffle-subflow/1.0.0/requirements.txt | 3 ++- shuffle-subflow/1.0.0/src/app.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/shuffle-subflow/1.0.0/requirements.txt b/shuffle-subflow/1.0.0/requirements.txt index 480d0c4b..41daa615 100644 --- a/shuffle-subflow/1.0.0/requirements.txt +++ b/shuffle-subflow/1.0.0/requirements.txt @@ -1 +1,2 @@ -requests==2.32.4 \ No newline at end of file +requests==2.32.4 +shuffle-sdk diff --git a/shuffle-subflow/1.0.0/src/app.py b/shuffle-subflow/1.0.0/src/app.py index a5ffebf3..adcb13cd 100644 --- a/shuffle-subflow/1.0.0/src/app.py +++ b/shuffle-subflow/1.0.0/src/app.py @@ -1,7 +1,7 @@ import json import requests -from walkoff_app_sdk.app_base import AppBase +from shuffle_sdk import AppBase class Subflow(AppBase): """ From 2fc1f795aff2e8b845c317d66d3caf1a228ecc61 Mon Sep 17 00:00:00 2001 From: yashsinghcodes Date: Fri, 19 Sep 2025 19:34:38 +0530 Subject: [PATCH 240/259] chore: bump up shuffle_sdk version --- active-directory/1.0.0/requirements.txt | 2 +- shuffle-ai/1.0.0/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/active-directory/1.0.0/requirements.txt b/active-directory/1.0.0/requirements.txt index 2c477e61..ef5f2d65 100644 --- a/active-directory/1.0.0/requirements.txt +++ b/active-directory/1.0.0/requirements.txt @@ -1,2 +1,2 @@ ldap3==2.9.1 -shuffle-sdk==0.0.28 +shuffle-sdk==0.0.30 diff --git a/shuffle-ai/1.0.0/requirements.txt b/shuffle-ai/1.0.0/requirements.txt index 6cf2c0d7..49a14d0e 100644 --- a/shuffle-ai/1.0.0/requirements.txt +++ b/shuffle-ai/1.0.0/requirements.txt @@ -1,4 +1,4 @@ -shuffle-sdk==0.0.28 +shuffle-sdk==0.0.30 pytesseract pdf2image From 1fc3c863ec1d8c6c4b3fda2498c4b04bcc33f7e3 Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 22 Sep 2025 14:19:10 +0200 Subject: [PATCH 241/259] More robust timeout parser --- http/1.4.0/src/app.py | 64 ++++++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/http/1.4.0/src/app.py b/http/1.4.0/src/app.py index b376c41c..95ee49f8 100755 --- a/http/1.4.0/src/app.py +++ b/http/1.4.0/src/app.py @@ -191,9 +191,12 @@ def GET(self, url, headers="", username="", password="", verify=True, http_proxy auth = requests.auth.HTTPBasicAuth(username, password) if not timeout: - timeout = 5 - if timeout: - timeout = int(timeout) + timeout = 25 + else: + try: + timeout = int(timeout) + except: + timeout = 25 if to_file == "true": to_file = True @@ -229,9 +232,12 @@ def POST(self, url, headers="", body="", username="", password="", verify=True, auth = requests.auth.HTTPBasicAuth(username, password) if not timeout: - timeout = 5 - if timeout: - timeout = int(timeout) + timeout = 25 + else: + try: + timeout = int(timeout) + except: + timeout = 25 if to_file == "true": to_file = True @@ -268,9 +274,12 @@ def PUT(self, url, headers="", body="", username="", password="", verify=True, h auth = requests.auth.HTTPBasicAuth(username, password) if not timeout: - timeout = 5 - if timeout: - timeout = int(timeout) + timeout = 25 + else: + try: + timeout = int(timeout) + except: + timeout = 25 if to_file == "true": to_file = True @@ -306,9 +315,12 @@ def PATCH(self, url, headers="", body="", username="", password="", verify=True, auth = requests.auth.HTTPBasicAuth(username, password) if not timeout: - timeout = 5 - if timeout: - timeout = int(timeout) + timeout = 25 + else: + try: + timeout = int(timeout) + except: + timeout = 25 if to_file == "true": to_file = True @@ -344,9 +356,12 @@ def DELETE(self, url, headers="", body="", username="", password="", verify=True auth = requests.auth.HTTPBasicAuth(username, password) if not timeout: - timeout = 5 - if timeout: - timeout = int(timeout) + timeout = 25 + else: + try: + timeout = int(timeout) + except: + timeout = 25 if to_file == "true": to_file = True @@ -382,9 +397,12 @@ def HEAD(self, url, headers="", body="", username="", password="", verify=True, auth = requests.auth.HTTPBasicAuth(username, password) if not timeout: - timeout = 5 - if timeout: - timeout = int(timeout) + timeout = 25 + else: + try: + timeout = int(timeout) + except: + timeout = 25 if to_file == "true": to_file = True @@ -420,10 +438,12 @@ def OPTIONS(self, url, headers="", body="", username="", password="", verify=Tru auth = requests.auth.HTTPBasicAuth(username, password) if not timeout: - timeout = 5 - - if timeout: - timeout = int(timeout) + timeout = 25 + else: + try: + timeout = int(timeout) + except: + timeout = 25 if to_file == "true": to_file = True From d511df37ef3ce1cca7656cceff1b68c94493a231 Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 22 Sep 2025 15:22:07 +0200 Subject: [PATCH 242/259] Rebuild with new sdk 0.0.31 --- active-directory/1.0.0/requirements.txt | 2 +- shuffle-ai/1.0.0/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/active-directory/1.0.0/requirements.txt b/active-directory/1.0.0/requirements.txt index ef5f2d65..0d9f9d76 100644 --- a/active-directory/1.0.0/requirements.txt +++ b/active-directory/1.0.0/requirements.txt @@ -1,2 +1,2 @@ +shuffle-sdk ldap3==2.9.1 -shuffle-sdk==0.0.30 diff --git a/shuffle-ai/1.0.0/requirements.txt b/shuffle-ai/1.0.0/requirements.txt index 49a14d0e..62bee6e9 100644 --- a/shuffle-ai/1.0.0/requirements.txt +++ b/shuffle-ai/1.0.0/requirements.txt @@ -1,4 +1,4 @@ -shuffle-sdk==0.0.30 +shuffle-sdk pytesseract pdf2image From 395c1064cf87950d82f6a768c88af76be866ff1d Mon Sep 17 00:00:00 2001 From: Hari Krishna Date: Thu, 16 Oct 2025 08:03:48 +0000 Subject: [PATCH 243/259] feat: add existence check for keys in cache --- shuffle-tools/1.2.0/src/app.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index c99aa240..83949ade 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -2304,6 +2304,13 @@ def set_cache_value(self, key, value, category=""): allvalues["key"] = key #allvalues["value"] = json.loads(json.dumps(value)) + allvalues["existed"] = False + if "keys_existed" in allvalues: + for key_info in allvalues["keys_existed"]: + if key_info["key"] == key: + allvalues["existed"] = key_info["existed"] + break + if (value.startswith("{") and value.endswith("}")) or (value.startswith("[") and value.endswith("]")): try: allvalues["value"] = json.loads(value) From 151577ecade330d9e2eb7c2018de8b285d621f17 Mon Sep 17 00:00:00 2001 From: Hari Krishna Date: Wed, 19 Nov 2025 10:57:36 +0000 Subject: [PATCH 244/259] update the authentication type to NTLM --- active-directory/1.0.0/src/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/active-directory/1.0.0/src/app.py b/active-directory/1.0.0/src/app.py index d7091b93..8b5e1245 100644 --- a/active-directory/1.0.0/src/app.py +++ b/active-directory/1.0.0/src/app.py @@ -6,6 +6,7 @@ Connection, MODIFY_REPLACE, ALL_ATTRIBUTES, + NTLM ) from ldap3.extend.microsoft.addMembersToGroups import ad_add_members_to_groups as addUsersInGroups @@ -31,8 +32,7 @@ def __ldap_connection(self, server, port, domain, login_user, password, use_ssl) login_dn = domain + "\\" + login_user s = Server(server, port=int(port), use_ssl=use_SSL) - c = Connection(s, user=login_dn, password=password, auto_bind=True) - + c = Connection(s, user=login_dn, password=password, authentication=NTLM, auto_bind=True) return c # Decode UserAccountControl code From 5640504ace97b17f43d942ac94c7525b50558a2e Mon Sep 17 00:00:00 2001 From: yashsinghcodes Date: Tue, 16 Dec 2025 13:52:13 +0530 Subject: [PATCH 245/259] chore: updated app_sdk version --- shuffle-tools/1.2.0/requirements.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shuffle-tools/1.2.0/requirements.txt b/shuffle-tools/1.2.0/requirements.txt index 89ca3320..f0d535ba 100644 --- a/shuffle-tools/1.2.0/requirements.txt +++ b/shuffle-tools/1.2.0/requirements.txt @@ -8,5 +8,4 @@ json2xml==5.0.5 ipaddress==1.0.23 google.auth==2.37.0 paramiko==3.5.0 -shuffle-sdk - +shuffle-sdk==0.0.31 From 88c3aa96c6b312ed22103208d926eab1669a5cb4 Mon Sep 17 00:00:00 2001 From: yashsinghcodes Date: Tue, 16 Dec 2025 14:15:09 +0530 Subject: [PATCH 246/259] fix: version conflict --- shuffle-tools/1.2.0/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shuffle-tools/1.2.0/requirements.txt b/shuffle-tools/1.2.0/requirements.txt index f0d535ba..950edceb 100644 --- a/shuffle-tools/1.2.0/requirements.txt +++ b/shuffle-tools/1.2.0/requirements.txt @@ -2,7 +2,7 @@ ioc_finder==7.3.0 py7zr==0.22.0 rarfile==4.2 pyminizip==0.2.6 -requests==2.32.4 +requests==2.32.3 xmltodict==0.14.2 json2xml==5.0.5 ipaddress==1.0.23 From 20f1a53774ef37479dcee73fdffd139a19085f50 Mon Sep 17 00:00:00 2001 From: yashsinghcodes Date: Tue, 16 Dec 2025 14:30:16 +0530 Subject: [PATCH 247/259] fix: added missing packages --- shuffle-tools/1.2.0/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shuffle-tools/1.2.0/Dockerfile b/shuffle-tools/1.2.0/Dockerfile index 40675132..fac3aae2 100644 --- a/shuffle-tools/1.2.0/Dockerfile +++ b/shuffle-tools/1.2.0/Dockerfile @@ -5,7 +5,7 @@ FROM frikky/shuffle:app_sdk as base FROM base as builder # Install all alpine build tools needed for our pip installs -RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev git +RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev git zlib-dev python3-dev # Install all of our pip packages in a single directory that we can copy to our base image later RUN mkdir /install From 4127de12f23ec823f966a9f021c900887a80af6b Mon Sep 17 00:00:00 2001 From: yashsinghcodes Date: Tue, 16 Dec 2025 15:07:20 +0530 Subject: [PATCH 248/259] fix: remove the pyzipper lib --- shuffle-tools/1.2.0/requirements.txt | 2 +- shuffle-tools/1.2.0/src/app.py | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/shuffle-tools/1.2.0/requirements.txt b/shuffle-tools/1.2.0/requirements.txt index 950edceb..4fe45011 100644 --- a/shuffle-tools/1.2.0/requirements.txt +++ b/shuffle-tools/1.2.0/requirements.txt @@ -1,7 +1,7 @@ ioc_finder==7.3.0 py7zr==0.22.0 rarfile==4.2 -pyminizip==0.2.6 +pyzipper==0.3.6 requests==2.32.3 xmltodict==0.14.2 json2xml==5.0.5 diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 83949ade..a50e405b 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -30,7 +30,7 @@ from google.auth import jwt import py7zr -import pyminizip +import pyzipper import rarfile import requests import tarfile @@ -1444,9 +1444,19 @@ def create_archive(self, file_ids, fileformat, name, password=None): if fileformat == "zip": archive_name = "archive.zip" if not name else name - pyminizip.compress_multiple( - paths, [], archive.name, password, 5 - ) + + pwd = password if isinstance(password, (bytes, bytearray)) else password.encode() + + with pyzipper.AESZipFile( + archive.name, + "w", + compression=pyzipper.ZIP_DEFLATED + ) as zf: + zf.setpassword(pwd) + zf.setencryption(pyzipper.WZ_AES, nbits=256) + + for path in paths: + zf.write(path, arcname=os.path.basename(path)) elif fileformat == "7zip": archive_name = "archive.7z" if not name else name From f7940e2d56acc2b170cf5fb3bf415ff01abd520e Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 18 Dec 2025 14:42:34 +0100 Subject: [PATCH 249/259] Changed cache names to datastore. Retaining functions for backwards compatibility --- shuffle-tools/1.2.0/api.yaml | 185 ++++++++++++++++++++++++++------- shuffle-tools/1.2.0/src/app.py | 14 ++- 2 files changed, 158 insertions(+), 41 deletions(-) diff --git a/shuffle-tools/1.2.0/api.yaml b/shuffle-tools/1.2.0/api.yaml index 301b9f51..9fdb9cf8 100644 --- a/shuffle-tools/1.2.0/api.yaml +++ b/shuffle-tools/1.2.0/api.yaml @@ -61,41 +61,7 @@ actions: schema: type: string - - name: check_cache_contains - description: Checks Shuffle cache whether a user-provided key contains a value. Returns ALL the values previously appended. - parameters: - - name: key - description: The key to get - required: true - multiline: false - example: "alert_ids" - schema: - type: string - - name: value - description: The value to check for and append if applicable - required: true - multiline: false - example: "1208301599081" - schema: - type: string - - name: append - description: Whether to auto-append the value if it doesn't exist in the cache - required: true - options: - - true - - false - multiline: false - example: "timestamp" - schema: - type: string - - name: category - description: The category to get the value from. Not required. - required: false - multiline: false - example: "tickets" - schema: - type: string - - name: get_cache_value + - name: get_datastore_value description: Get a value saved to your organization in Shuffle parameters: - name: key @@ -115,7 +81,7 @@ actions: returns: schema: type: string - - name: set_cache_value + - name: set_datastore_value description: Set a value to be saved to your organization in Shuffle. parameters: - name: key @@ -142,7 +108,8 @@ actions: returns: schema: type: string - - name: delete_cache_value + + - name: delete_datastore_value description: Delete a value saved to your organization in Shuffle parameters: - name: key @@ -569,6 +536,13 @@ actions: example: "EventID,username\n4137,frikky" schema: type: string + - name: category + description: The category the file belongs to + required: false + multiline: false + example: "yara-rules" + schema: + type: string - name: download_remote_file description: Downloads a file from a URL parameters: @@ -1269,6 +1243,143 @@ actions: returns: schema: type: string + + - name: check_datastore_contains + description: We recommend "Search datastore category" instead. Checks Shuffle datastore whether a user-provided key contains a value. Returns ALL the values previously appended. + parameters: + - name: key + description: The key to get + required: true + multiline: false + example: "alert_ids" + schema: + type: string + - name: value + description: The value to check for and append if applicable + required: true + multiline: false + example: "1208301599081" + schema: + type: string + - name: append + description: Whether to auto-append the value if it doesn't exist in the cache + required: true + options: + - true + - false + multiline: false + example: "timestamp" + schema: + type: string + - name: category + description: The category to get the value from. Not required. + required: false + multiline: false + example: "tickets" + schema: + type: string + - name: get_cache_value + description: Get a value saved to your organization in Shuffle. Deprecated for "get_datastore_value" + parameters: + - name: key + description: The key to get + required: true + multiline: false + example: "timestamp" + schema: + type: string + - name: category + description: The category to get the value from. Not required. + required: false + multiline: false + example: "tickets" + schema: + type: string + returns: + schema: + type: string + - name: delete_cache_value + description: Delete a value saved to your organization in Shuffle. Deprecated for "delete_datastore_value" + parameters: + - name: key + description: The key to delete + required: true + multiline: false + example: "timestamp" + schema: + type: string + - name: category + description: The category to get the value from. Not required. + required: false + multiline: false + example: "tickets" + schema: + type: string + returns: + schema: + type: string + + - name: set_cache_value + description: Set a value to be saved to your organization in Shuffle. Deprecated for "set_datastore_value" + parameters: + - name: key + description: The key to set the value for + required: true + multiline: false + example: "timestamp" + schema: + type: string + - name: value + description: The value to set + required: true + multiline: true + example: "1621959545" + schema: + type: string + - name: category + description: The category to get the value from. Not required. + required: false + multiline: false + example: "tickets" + schema: + type: string + returns: + schema: + type: string + - name: check_cache_contains + description: Checks Shuffle cache whether a user-provided key contains a value. Returns ALL the values previously appended. Deprecated for "check datastore contains" + parameters: + - name: key + description: The key to get + required: true + multiline: false + example: "alert_ids" + schema: + type: string + - name: value + description: The value to check for and append if applicable + required: true + multiline: false + example: "1208301599081" + schema: + type: string + - name: append + description: Whether to auto-append the value if it doesn't exist in the cache + required: true + options: + - true + - false + multiline: false + example: "timestamp" + schema: + type: string + - name: category + description: The category to get the value from. Not required. + required: false + multiline: false + example: "tickets" + schema: + type: string #- name: parse_ioc_new # description: Parse IOC's based on https://github.com/fhightower/ioc-finder # parameters: diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 83949ade..284450a1 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -1125,7 +1125,7 @@ def delete_file(self, file_id): ) return ret.text - def create_file(self, filename, data): + def create_file(self, filename, data, category=""): try: if str(data).startswith("b'") and str(data).endswith("'"): data = data[2:-1] @@ -1144,6 +1144,7 @@ def create_file(self, filename, data): filedata = { "filename": filename, "data": data, + "namespace": category, } fileret = self.set_files([filedata]) @@ -1158,8 +1159,9 @@ def list_file_category_ids(self, file_category): return self.get_file_category_ids(file_category) # Input is WAS a file, hence it didn't get the files - def get_file_value(self, filedata): - filedata = self.get_file(filedata) + # Category doesn't matter as it uses file ID, which is unique anyway + def get_file_value(self, filedata, category=""): + filedata = self.get_file(filedata, category) if filedata is None: return { "success": False, @@ -1190,7 +1192,7 @@ def get_file_value(self, filedata): "size": len(filedata["data"]), } - def download_remote_file(self, url, custom_filename=""): + def download_remote_file(self, url, custom_filename="", category=""): ret = requests.get(url, verify=False) # nosec filename = url.split("/")[-1] if "?" in filename: @@ -1204,6 +1206,7 @@ def download_remote_file(self, url, custom_filename=""): { "filename": filename, "data": ret.content, + "namespace": category, } ] ) @@ -1827,6 +1830,9 @@ def escape_html(self, input_data): result = markupsafe.escape(mapping) return mapping + def check_datastore_contains(self, key, value, append, category=""): + return check_cache_contains(self, key, value, append, category) + def check_cache_contains(self, key, value, append, category=""): org_id = self.full_execution["workflow"]["execution_org"]["id"] url = "%s/api/v1/orgs/%s/get_cache" % (self.url, org_id) From b7d9a2f7146892443e9766ba5510f8c61d1517c3 Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 18 Dec 2025 15:30:20 +0100 Subject: [PATCH 250/259] Added a get_ioc function to shuffle tools that usees datastore locally --- shuffle-tools/1.2.0/api.yaml | 22 +++++++++++++++ shuffle-tools/1.2.0/src/app.py | 50 +++++++++++++++++++++++++++++++++- 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/shuffle-tools/1.2.0/api.yaml b/shuffle-tools/1.2.0/api.yaml index 9fdb9cf8..ebbf7c3e 100644 --- a/shuffle-tools/1.2.0/api.yaml +++ b/shuffle-tools/1.2.0/api.yaml @@ -156,6 +156,7 @@ actions: returns: schema: type: string + #- name: send_email_shuffle # description: Send an email from Shuffle # parameters: @@ -1400,6 +1401,27 @@ actions: # returns: # schema: # type: string + # + - name: get_ioc + description: Get IOC's saved to your organization in Shuffle + parameters: + - name: ioc + description: The IOC to look for in Shuffle's datastore + required: true + multiline: true + example: "timestamp" + schema: + type: string + - name: data_type + description: The data type to get the IOC from. Discovered if not passed. + required: false + multiline: false + example: "ip" + schema: + type: string + returns: + schema: + type: string large_image: data:image/png;base64,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 # yamllint disable-line rule:line-length diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 0a2c48a9..0dba5d9b 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -2149,6 +2149,55 @@ def delete_cache_value(self, key, category=""): def get_datastore_value(self, key, category=""): return self.get_cache_value(key, category=category) + def get_ioc(self, ioc, data_type=""): + if len(data_type) == 0: + ioc_types = ["domains", "urls", "email_addresses", "ipv4s", "ipv6s", "ipv4_cidrs", "md5s", "sha256s", "sha1s", "cves"] + + iocs = find_iocs(str(input_string)) + for key, value in iocs.items(): + for item in value: + if item.lower() == ioc.lower(): + print("[DEBUG] Found IOC %s in type %s" % (ioc, key)) + data_type = key[:-1] + break + + if len(data_type) > 0: + break + + org_id = self.full_execution["workflow"]["execution_org"]["id"] + url = "%s/api/v1/orgs/%s/get_cache" % (self.url, org_id) + data = { + "workflow_id": self.full_execution["workflow"]["id"], + "execution_id": self.current_execution_id, + "authorization": self.authorization, + "org_id": org_id, + "key": str(key), + "category": "ioc_%s" % data_type.replace(" ", "_").lower(), + } + + value = requests.post(url, json=data, verify=False) + try: + allvalues = value.json() + allvalues["key"] = key + + if allvalues["success"] == True and len(allvalues["value"]) > 0: + allvalues["found"] = True + else: + allvalues["success"] = True + allvalues["found"] = False + + try: + parsedvalue = json.loads(allvalues["value"]) + allvalues["value"] = parsedvalue + + except: + pass + + return json.dumps(allvalues) + except: + self.logger.info("Value couldn't be parsed, or json dump of value failed") + return value.text + def get_cache_value(self, key, category=""): org_id = self.full_execution["workflow"]["execution_org"]["id"] url = "%s/api/v1/orgs/%s/get_cache" % (self.url, org_id) @@ -2848,7 +2897,6 @@ def parse_ioc(self, input_string, input_type="all"): print("Invalid key: %s" % key) continue - print(key, value) if len(value) == 0: continue From e0fb386373906cff541b13893caa4f587c14f251 Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 18 Dec 2025 15:58:31 +0100 Subject: [PATCH 251/259] Minor fix --- shuffle-tools/1.2.0/src/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 0dba5d9b..85275cf2 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -2153,7 +2153,7 @@ def get_ioc(self, ioc, data_type=""): if len(data_type) == 0: ioc_types = ["domains", "urls", "email_addresses", "ipv4s", "ipv6s", "ipv4_cidrs", "md5s", "sha256s", "sha1s", "cves"] - iocs = find_iocs(str(input_string)) + iocs = find_iocs(str(ioc)) for key, value in iocs.items(): for item in value: if item.lower() == ioc.lower(): From 2f7c27e2b3530fb39609656e5061f1e1a719ded1 Mon Sep 17 00:00:00 2001 From: yashsinghcodes Date: Mon, 19 Jan 2026 16:54:46 +0530 Subject: [PATCH 252/259] fix:unsupported hash type MD4 --- active-directory/1.0.0/requirements.txt | 1 + active-directory/1.0.0/src/app.py | 219 +++++++++++++++++------- 2 files changed, 162 insertions(+), 58 deletions(-) diff --git a/active-directory/1.0.0/requirements.txt b/active-directory/1.0.0/requirements.txt index 0d9f9d76..ad59907e 100644 --- a/active-directory/1.0.0/requirements.txt +++ b/active-directory/1.0.0/requirements.txt @@ -1,2 +1,3 @@ shuffle-sdk ldap3==2.9.1 +pycryptodome diff --git a/active-directory/1.0.0/src/app.py b/active-directory/1.0.0/src/app.py index 8b5e1245..67f8ed2c 100644 --- a/active-directory/1.0.0/src/app.py +++ b/active-directory/1.0.0/src/app.py @@ -1,19 +1,24 @@ import json +import hashlib import ldap3 import asyncio -from ldap3 import ( - Server, - Connection, - MODIFY_REPLACE, - ALL_ATTRIBUTES, - NTLM -) +from ldap3 import Server, Connection, MODIFY_REPLACE, ALL_ATTRIBUTES, NTLM + +try: + from Crypto.Hash import MD4 as CryptoMD4 +except ImportError: + CryptoMD4 = None -from ldap3.extend.microsoft.addMembersToGroups import ad_add_members_to_groups as addUsersInGroups -from ldap3.extend.microsoft.removeMembersFromGroups import ad_remove_members_from_groups as removeUsersFromGroups +from ldap3.extend.microsoft.addMembersToGroups import ( + ad_add_members_to_groups as addUsersInGroups, +) +from ldap3.extend.microsoft.removeMembersFromGroups import ( + ad_remove_members_from_groups as removeUsersFromGroups, +) from shuffle_sdk import AppBase + class ActiveDirectory(AppBase): __version__ = "1.0.1" app_name = "Active Directory" # this needs to match "name" in api.yaml @@ -28,11 +33,28 @@ def __init__(self, redis, logger, console_logger=None): super().__init__(redis, logger, console_logger) def __ldap_connection(self, server, port, domain, login_user, password, use_ssl): - use_SSL = False if use_ssl.lower() == "false" else True + use_SSL = False if use_ssl.lower() == "false" else True login_dn = domain + "\\" + login_user s = Server(server, port=int(port), use_ssl=use_SSL) - c = Connection(s, user=login_dn, password=password, authentication=NTLM, auto_bind=True) + + if CryptoMD4 and not getattr(hashlib, "__active_directory_md4_patch__", False): + try: + import ldap3.utils.ntlm as ldap3_ntlm + + def _md4_hash(data): + md4 = CryptoMD4.new() + md4.update(data) + return md4.digest() + + ldap3_ntlm.hashlib.md4 = _md4_hash + hashlib.__active_directory_md4_patch__ = True + except Exception: + pass + + c = Connection( + s, user=login_dn, password=password, authentication=NTLM, auto_bind=True + ) return c # Decode UserAccountControl code @@ -137,21 +159,28 @@ def user_attributes( result = json.loads(c.response_to_json()) if len(result["entries"]) == 0: - return json.dumps({ - "success": False, - "result": result, - "reason": "No user found for %s" % samaccountname, - }) + return json.dumps( + { + "success": False, + "result": result, + "reason": "No user found for %s" % samaccountname, + } + ) except Exception as e: - return json.dumps({ - "success": False, - "reason": "Failed to get users in user attributes: %s" % e, - }) - + return json.dumps( + { + "success": False, + "reason": "Failed to get users in user attributes: %s" % e, + } + ) result = result["entries"][0] - result["attributes"]["userAccountControl"] = self.__getUserAccountControlAttributes(result["attributes"]["userAccountControl"]) + result["attributes"]["userAccountControl"] = ( + self.__getUserAccountControlAttributes( + result["attributes"]["userAccountControl"] + ) + ) return json.dumps(result) @@ -180,7 +209,19 @@ def set_password( server, port, domain, login_user, password, use_ssl ) - result = json.loads( self.user_attributes( server, port, domain, login_user, password, base_dn, use_ssl, samaccountname, search_base,)) + result = json.loads( + self.user_attributes( + server, + port, + domain, + login_user, + password, + base_dn, + use_ssl, + samaccountname, + search_base, + ) + ) user_dn = result["dn"] c.extend.microsoft.modify_password(user_dn, new_password) @@ -243,7 +284,6 @@ def enable_user( samaccountname, search_base, ): - if search_base: base_dn = search_base @@ -299,7 +339,6 @@ def disable_user( samaccountname, search_base, ): - if search_base: base_dn = search_base @@ -326,7 +365,6 @@ def disable_user( "success": False, "reason": "Failed to get result attributes: %s" % e, } - if "ACCOUNTDISABLED" in userAccountControl: try: @@ -362,8 +400,18 @@ def disable_user( "reason": "Failed adding ACCOUNTDISABLED to user: %s" % e, } - def lock_user(self,server,domain,port,login_user,password,base_dn,use_ssl,samaccountname,search_base): - + def lock_user( + self, + server, + domain, + port, + login_user, + password, + base_dn, + use_ssl, + samaccountname, + search_base, + ): if search_base: base_dn = search_base @@ -372,19 +420,29 @@ def lock_user(self,server,domain,port,login_user,password,base_dn,use_ssl,samacc c.search(base_dn, f"(SAMAccountName={samaccountname})") if len(c.entries) == 0: - return {"success":"false","message":f"User {samaccountname} not found"} + return {"success": "false", "message": f"User {samaccountname} not found"} user_dn = c.entries[0].entry_dn - c.modify(user_dn, {'userAccountControl':[(MODIFY_REPLACE,[514])]}) + c.modify(user_dn, {"userAccountControl": [(MODIFY_REPLACE, [514])]}) result = c.result result["success"] = True return result - - def unlock_user(self,server,domain,port,login_user,password,base_dn,use_ssl,samaccountname,search_base): - + + def unlock_user( + self, + server, + domain, + port, + login_user, + password, + base_dn, + use_ssl, + samaccountname, + search_base, + ): if search_base: base_dn = search_base @@ -393,44 +451,72 @@ def unlock_user(self,server,domain,port,login_user,password,base_dn,use_ssl,sama c.search(base_dn, f"(SAMAccountName={samaccountname})") if len(c.entries) == 0: - return {"success":"false","message":f"User {samaccountname} not found"} + return {"success": "false", "message": f"User {samaccountname} not found"} user_dn = c.entries[0].entry_dn - c.modify(user_dn, {'userAccountControl':[(MODIFY_REPLACE,[0])]}) + c.modify(user_dn, {"userAccountControl": [(MODIFY_REPLACE, [0])]}) result = c.result result["success"] = True return result - - def change_user_password_at_next_login(self,server,domain,port,login_user,password,base_dn,use_ssl,samaccountname,search_base,new_user_password,repeat_new_user_password): - + + def change_user_password_at_next_login( + self, + server, + domain, + port, + login_user, + password, + base_dn, + use_ssl, + samaccountname, + search_base, + new_user_password, + repeat_new_user_password, + ): if search_base: base_dn = search_base if str(new_user_password) != str(repeat_new_user_password): - return {"success":"false","message":"new_user_password and repeat_new_user_password does not match."} + return { + "success": "false", + "message": "new_user_password and repeat_new_user_password does not match.", + } c = self.__ldap_connection(server, port, domain, login_user, password, use_ssl) c.search(base_dn, f"(SAMAccountName={samaccountname})") if len(c.entries) == 0: - return {"success":"false","message":f"User {samaccountname} not found"} + return {"success": "false", "message": f"User {samaccountname} not found"} user_dn = c.entries[0].entry_dn - c.modify(user_dn, {'pwdLastSet':(MODIFY_REPLACE, [0])}) - c.extend.microsoft.modify_password(user_dn, new_user_password.encode('utf-16-le')) + c.modify(user_dn, {"pwdLastSet": (MODIFY_REPLACE, [0])}) + c.extend.microsoft.modify_password( + user_dn, new_user_password.encode("utf-16-le") + ) result = c.result result["success"] = True return result - def add_user_to_group(self, server, domain, port, login_user, password, base_dn, use_ssl, samaccountname, search_base, group_name): - + def add_user_to_group( + self, + server, + domain, + port, + login_user, + password, + base_dn, + use_ssl, + samaccountname, + search_base, + group_name, + ): if search_base: base_dn = search_base @@ -438,24 +524,38 @@ def add_user_to_group(self, server, domain, port, login_user, password, base_dn, c.search(base_dn, f"(SAMAccountName={samaccountname})") if len(c.entries) == 0: - return {"success":"false","message":f"User {samaccountname} not found"} + return {"success": "false", "message": f"User {samaccountname} not found"} user_dn = c.entries[0].entry_dn - search_filter = f'(&(objectClass=group)(cn={group_name}))' + search_filter = f"(&(objectClass=group)(cn={group_name}))" c.search(base_dn, search_filter, attributes=["distinguishedName"]) if len(c.entries) == 0: - return {"success":"false","message":f"Group {group_name} not found"} + return {"success": "false", "message": f"Group {group_name} not found"} group_dn = c.entries[0]["distinguishedName"] print(group_dn) - res = addUsersInGroups(c, user_dn, str(group_dn),fix=True) + res = addUsersInGroups(c, user_dn, str(group_dn), fix=True) if res == True: - return {"success":"true","message":f"User {samaccountname} was added to group {group_name}"} + return { + "success": "true", + "message": f"User {samaccountname} was added to group {group_name}", + } else: - return {"success":"false","message":f"Could not add user to group"} + return {"success": "false", "message": f"Could not add user to group"} - def remove_user_from_group(self, server, domain, port, login_user, password, base_dn, use_ssl, samaccountname, search_base, group_name): - + def remove_user_from_group( + self, + server, + domain, + port, + login_user, + password, + base_dn, + use_ssl, + samaccountname, + search_base, + group_name, + ): if search_base: base_dn = search_base @@ -463,21 +563,24 @@ def remove_user_from_group(self, server, domain, port, login_user, password, bas c.search(base_dn, f"(SAMAccountName={samaccountname})") if len(c.entries) == 0: - return {"success":"false","message":f"User {samaccountname} not found"} + return {"success": "false", "message": f"User {samaccountname} not found"} user_dn = c.entries[0].entry_dn - search_filter = f'(&(objectClass=group)(cn={group_name}))' + search_filter = f"(&(objectClass=group)(cn={group_name}))" c.search(base_dn, search_filter, attributes=["distinguishedName"]) if len(c.entries) == 0: - return {"success":"false","message":f"Group {group_name} not found"} + return {"success": "false", "message": f"Group {group_name} not found"} group_dn = c.entries[0]["distinguishedName"] print(group_dn) - res = removeUsersFromGroups(c, user_dn, str(group_dn),fix=True) + res = removeUsersFromGroups(c, user_dn, str(group_dn), fix=True) if res == True: - return {"success":"true","message":f"User {samaccountname} was removed from group {group_name}"} + return { + "success": "true", + "message": f"User {samaccountname} was removed from group {group_name}", + } else: - return {"success":"false","message":f"Could not remove user to group"} + return {"success": "false", "message": f"Could not remove user to group"} if __name__ == "__main__": From 122793913b991ab59fb532c5a98a98c34d427aae Mon Sep 17 00:00:00 2001 From: yashsinghcodes Date: Sun, 25 Jan 2026 19:28:18 +0530 Subject: [PATCH 253/259] added AGENTS.md for agent context --- AGENTS.md | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..271c0fc2 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,76 @@ +# Repository Guidelines + +## Project Structure & Module Organization +This repository hosts Shuffle app implementations. Each app lives in a top-level folder named after the integration (e.g., `aws-ec2/`), and each release is versioned under a subfolder like `1.0.0/`. A typical app version contains: + +- `src/app.py`: the Shuffle SDK entry point. +- `api.yaml`: OpenAPI definition used by Shuffle. +- `requirements.txt`: Python dependencies for the app. +- `Dockerfile`: container build instructions for the app. +- `README.md`: app-specific usage and action documentation. +- Optional assets such as screenshots (`*.png`). + +In `api.yaml`, prefer an `authentication` block for shared credentials (URL, tokens, keys). Actions should only include auth parameters when they truly differ per call. + +## Build, Test, and Development Commands +Apps are built and run container-first via the Shuffle SDK image. From an app version directory: + +- `docker build -t shuffle-: .`: build the app image. +- `docker run --rm shuffle-:`: run the app container locally. + +For quick iteration on code, you can also run the Python entrypoint in a virtualenv: + +- `pip install -r requirements.txt` +- `python src/app.py --log-level DEBUG` + +## Coding Style & Naming Conventions +Use 4-space indentation and standard Python style. Keep functions `snake_case`, classes `CamelCase`, and constants `UPPER_SNAKE_CASE`. Match existing patterns in `src/app.py` and keep action names aligned with `api.yaml`. + +## Creating New Shuffle Apps (Agent Workflow) +Use an existing app as a template (e.g., `http/1.4.0/` or `aws-ec2/1.0.0/`) and follow the same folder layout. A minimal, working app version should include: + +- `api.yaml`: action definitions, parameters, and examples. +- `src/app.py`: class extending the Shuffle SDK (`shuffle_sdk.AppBase`). +- `requirements.txt`: third-party dependencies. +- `Dockerfile`: built on `frikky/shuffle:app_sdk`. + +When adding actions, ensure the `api.yaml` action name matches the method name in `src/app.py` and parameter names align exactly. Keep input parsing defensive (strings vs JSON), and return JSON-serializable results. For HTTP integrations, centralize auth and base URL handling and add a TLS `verify` option. If a service requires special payloads (e.g., ADF for Jira), accept JSON strings and pass through unchanged. Keep `api.yaml` examples realistic because they show up in the Shuffle UI. + +## Authentication & App Configuration +Most apps declare credentials in `api.yaml` under `authentication:` so Shuffle injects them automatically. In code, read those values as normal action arguments (Shuffle passes them into each action). Prefer a single auth helper in `src/app.py` (e.g., `_auth()` for tokens, `_build_api_base()` for base URLs) and reuse it across actions. If an integration supports multiple auth modes (token vs password), accept both and choose the provided one. + +Prefer small, focused actions (create, update, list, search) and document auth requirements and examples in the app `README.md`. + +## Manual Python App Notes (From Shuffle Docs) +- **SDK image choices:** Shuffle provides Alpine (slim), Kali (security tooling), and Blackarch (kitchen‑sink). This repo’s Dockerfiles typically use `frikky/shuffle:app_sdk` (Alpine‑based) unless a toolset requires otherwise. +- **Directory layout:** `api.yaml`, `Dockerfile`, `requirements.txt`, `README.md`, and `src/app.py` are expected in each app version. Complex apps can add additional modules under `src/` and import them from `app.py`. +- **Actions & wiring:** Every action in `api.yaml` must map to a method in `src/app.py` with the same name and argument list. Authentication parameters are passed into each action automatically when declared under `authentication:`. +- **Utility helpers:** In `AppBase`, you can use `self.get_file`, `self.set_files`, `self.update_file`, and cache helpers `self.get_cache`, `self.set_cache`, `self.delete_cache` for file and key/value workflows. +- **Prototyping:** Build and test your Python logic locally first, then wire it into `src/app.py`. Keep return values JSON‑serializable so Shuffle can consume them. +- **Upload & hotload:** After a prototype works, upload it to Shuffle (cloud) or hotload locally (on‑prem) by rebuilding the app image. Local Docker rebuilds are faster for iteration. + +## Testing, Hotloading, and CI/CD +- **Cloud upload test:** Use the Upload App API to add the app to your org, then run a workflow to validate actions. +- **Local hotload (on‑prem):** Place the app folder in `shuffle-apps/`, set `SHUFFLE_APP_HOTLOAD_FOLDER=./shuffle-apps`, then use the hot reload button in the UI. Allow ~20 seconds for the reload to complete. +- **Update workflow deps:** If you update an existing app version, remove and re‑add the app in any workflows that reference it. +- **Fast local iteration:** After the first upload, rebuild locally: `docker images | grep ` then `docker build . -t `. +- **CI/CD pattern:** Create a test workflow, upload a test app version, run the workflow via API, and validate `workflowexecution.workflow.validation.valid` before promoting. + +## Publishing Apps +- **OpenAPI apps:** Upload to your instance, then use the `/apps` page to publish so it appears on `shuffler.io`. +- **Python apps:** Fork `https://github.com/frikky/shuffle-apps`, add your app, and open a pull request to upstream. + +## Testing Guidelines +There is no repository-wide test suite. If you add tests for a specific app, keep them alongside the app version (e.g., `aws-ec2/1.0.0/tests/`) and document how to run them in that app’s `README.md`. + +## Commit & Pull Request Guidelines +Commit messages are short and descriptive, sometimes using a prefix like `fix:`. Follow that style and keep commits scoped to a single app/version when possible. + +For pull requests, include: + +- A clear description of the change and impacted app/version path. +- Updated `README.md` or `api.yaml` when behavior changes. +- Screenshots/assets if user-facing output or UI-related docs are affected. + +## Security & Configuration Tips +Many apps require API keys or credentials. Do not commit secrets; use environment variables or Shuffle configuration fields instead, and document required inputs in the app’s `README.md`. From 1bc69724965a0df668cacdd414564c628d79389d Mon Sep 17 00:00:00 2001 From: yashsinghcodes Date: Sat, 7 Feb 2026 14:00:11 +0530 Subject: [PATCH 254/259] option to not return cache values --- shuffle-tools/1.2.0/api.yaml | 26 + shuffle-tools/1.2.0/src/app.py | 884 +++++++++++++++++++-------------- 2 files changed, 548 insertions(+), 362 deletions(-) diff --git a/shuffle-tools/1.2.0/api.yaml b/shuffle-tools/1.2.0/api.yaml index ebbf7c3e..ab5337ee 100644 --- a/shuffle-tools/1.2.0/api.yaml +++ b/shuffle-tools/1.2.0/api.yaml @@ -1279,6 +1279,20 @@ actions: example: "tickets" schema: type: string + - name: return_values + description: Whether to include the cache values in the response + required: false + options: + - true + - false + multiline: false + example: "false" + value: "true" + schema: + type: string + returns: + schema: + type: string - name: get_cache_value description: Get a value saved to your organization in Shuffle. Deprecated for "get_datastore_value" parameters: @@ -1300,6 +1314,7 @@ actions: schema: type: string - name: delete_cache_value + description: Delete a value saved to your organization in Shuffle. Deprecated for "delete_datastore_value" parameters: - name: key @@ -1381,6 +1396,17 @@ actions: example: "tickets" schema: type: string + - name: return_values + description: Whether to include the cache values in the response + required: false + options: + - true + - false + multiline: false + example: "false" + value: "true" + schema: + type: string #- name: parse_ioc_new # description: Parse IOC's based on https://github.com/fhightower/ioc-finder # parameters: diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 85275cf2..e1bbdac3 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -41,15 +41,16 @@ import concurrent.futures import multiprocessing -#from walkoff_app_sdk.app_base import AppBase +# from walkoff_app_sdk.app_base import AppBase from shuffle_sdk import AppBase -# Override exit(), sys.exit, and os._exit +# Override exit(), sys.exit, and os._exit # sys.exit() can be caught, meaning we can have a custom handler for it builtins.exit = sys.exit os.exit = sys.exit os._exit = sys.exit + class Tools(AppBase): __version__ = "1.2.0" app_name = ( @@ -86,7 +87,7 @@ def base64_conversion(self, string, operation): # Decode the base64 into an image and upload it as a file decoded_bytes = base64.b64decode(string) - # Make the bytes into unicode escaped bytes + # Make the bytes into unicode escaped bytes # UnicodeDecodeError - 'utf-8' codec can't decode byte 0x89 in position 0: invalid start byte try: decoded_bytes = str(decoded_bytes, "utf-8") @@ -96,7 +97,7 @@ def base64_conversion(self, string, operation): filename = "base64_image.png" file = { "filename": filename, - "data": decoded_bytes, + "data": decoded_bytes, } fileret = self.set_files([file]) @@ -107,7 +108,6 @@ def base64_conversion(self, string, operation): return value elif operation == "decode": - if "-" in string: string = string.replace("-", "+", -1) @@ -118,18 +118,19 @@ def base64_conversion(self, string, operation): if len(string) % 4 != 0: string += "=" * (4 - len(string) % 4) - # For loop this. It's stupid. - decoded_bytes = "" + decoded_bytes = "" try: decoded_bytes = base64.b64decode(string) except Exception as e: - return json.dumps({ - "success": False, - "reason": "Invalid Base64 - %s" % e, - }) + return json.dumps( + { + "success": False, + "reason": "Invalid Base64 - %s" % e, + } + ) - #if "incorrect padding" in str(e).lower(): + # if "incorrect padding" in str(e).lower(): # try: # decoded_bytes = base64.b64decode(string + "=") # except Exception as e: @@ -144,7 +145,6 @@ def base64_conversion(self, string, operation): # if "incorrect padding" in str(e).lower(): # return "Invalid Base64" - try: decoded_bytes = str(decoded_bytes, "utf-8") except: @@ -199,7 +199,6 @@ def send_sms_shuffle(self, apikey, phone_numbers, body): def send_email_shuffle(self, apikey, recipients, subject, body, attachments=""): recipients = self.parse_list_internal(recipients) - targets = [recipients] if ", " in recipients: targets = recipients.split(", ") @@ -207,9 +206,9 @@ def send_email_shuffle(self, apikey, recipients, subject, body, attachments=""): targets = recipients.split(",") data = { - "targets": targets, - "subject": subject, - "body": body, + "targets": targets, + "subject": subject, + "body": body, "type": "alert", "email_app": True, } @@ -222,11 +221,10 @@ def send_email_shuffle(self, apikey, recipients, subject, body, attachments=""): for item in attachments: new_file = self.get_file(file_ids) files.append(new_file) - + data["attachments"] = files except Exception as e: pass - url = "https://shuffler.io/functions/sendmail" headers = {"Authorization": "Bearer %s" % apikey} @@ -249,7 +247,8 @@ def dedup_and_merge(self, key, value, timeout, set_skipped=True): response = { "success": False, "datastore_key": cachekey, - "info": "All keys from the last %d seconds with the key '%s' have been merged. The result was set to SKIPPED in all other actions." % (timeout, key), + "info": "All keys from the last %d seconds with the key '%s' have been merged. The result was set to SKIPPED in all other actions." + % (timeout, key), "timeout": timeout, "original_value": value, "all_values": [], @@ -272,7 +271,7 @@ def dedup_and_merge(self, key, value, timeout, set_skipped=True): found_cache["value"].append(value) if "created" in found_cache: if found_cache["created"] + timeout + 3 < time.time(): - set_skipped = False + set_skipped = False response["success"] = True response["all_values"] = found_cache["value"] @@ -280,24 +279,37 @@ def dedup_and_merge(self, key, value, timeout, set_skipped=True): return json.dumps(response) else: - self.logger.info("Dedup-key is already handled in another workflow with timeout %d" % timeout) + self.logger.info( + "Dedup-key is already handled in another workflow with timeout %d" + % timeout + ) self.set_cache(cachekey, json.dumps(found_cache["value"])) if set_skipped == True: self.action_result["status"] = "SKIPPED" - self.action_result["result"] = json.dumps({ - "status": False, - "reason": "Dedup-key is already handled in another workflow with timeout %d" % timeout, - }) + self.action_result["result"] = json.dumps( + { + "status": False, + "reason": "Dedup-key is already handled in another workflow with timeout %d" + % timeout, + } + ) - self.send_result(self.action_result, {"Authorization": "Bearer %s" % self.authorization}, "/api/v1/streams") + self.send_result( + self.action_result, + {"Authorization": "Bearer %s" % self.authorization}, + "/api/v1/streams", + ) return found_cache parsedvalue = [value] resp = self.set_cache(cachekey, json.dumps(parsedvalue)) - self.logger.info("Sleeping for %d seconds while waiting for cache to fill up elsewhere" % timeout) + self.logger.info( + "Sleeping for %d seconds while waiting for cache to fill up elsewhere" + % timeout + ) time.sleep(timeout) found_cache = self.get_cache(cachekey) @@ -307,7 +319,6 @@ def dedup_and_merge(self, key, value, timeout, set_skipped=True): self.delete_cache(cachekey) return json.dumps(response) - # https://github.com/fhightower/ioc-finder def parse_file_ioc(self, file_ids, input_type="all"): def parse(data): @@ -326,7 +337,8 @@ def parse(data): for subitem in subvalue: data = { "data": subitem, - "data_type": "%s_%s" % (key[:-1], subkey), + "data_type": "%s_%s" + % (key[:-1], subkey), } if data not in newarray: newarray.append(data) @@ -405,10 +417,7 @@ def set_json_key(self, json_object, key, value): try: json_object = json.loads(json_object) except json.decoder.JSONDecodeError as e: - return { - "success": False, - "reason": "Item is not valid JSON" - } + return {"success": False, "reason": "Item is not valid JSON"} if isinstance(json_object, list): if len(json_object) == 1: @@ -416,16 +425,15 @@ def set_json_key(self, json_object, key, value): else: return { "success": False, - "reason": "Item is valid JSON, but can't handle lists. Use .#" + "reason": "Item is valid JSON, but can't handle lists. Use .#", } - #if not isinstance(json_object, object): + # if not isinstance(json_object, object): # return { # "success": False, # "reason": "Item is not valid JSON (2)" # } - if isinstance(value, str): try: value = json.loads(value) @@ -435,7 +443,7 @@ def set_json_key(self, json_object, key, value): # Handle JSON paths if "." in key: base_object = json.loads(json.dumps(json_object)) - #base_object.output.recipients.notificationEndpointIds = ... + # base_object.output.recipients.notificationEndpointIds = ... keys = key.split(".") if len(keys) >= 1: @@ -444,14 +452,14 @@ def set_json_key(self, json_object, key, value): # This is awful :) buildstring = "base_object" for subkey in keys: - buildstring += f"[\"{subkey}\"]" + buildstring += f'["{subkey}"]' buildstring += f" = {value}" - #output = + # output = exec(buildstring) json_object = base_object - #json_object[first_object] = base_object + # json_object[first_object] = base_object else: json_object[key] = value @@ -520,9 +528,9 @@ def replace_value_from_dictionary(self, input_data, mapping, default_value=""): except: self.logger.info(f"Failed mapping output data for key {key}") - return input_data + return input_data - # Changed with 1.1.0 to run with different returns + # Changed with 1.1.0 to run with different returns def regex_capture_group(self, input_data, regex): try: returnvalues = { @@ -533,8 +541,8 @@ def regex_capture_group(self, input_data, regex): found = False for item in matches: if isinstance(item, str): - found = True - name = "group_0" + found = True + name = "group_0" try: returnvalues[name].append(item) except: @@ -542,7 +550,7 @@ def regex_capture_group(self, input_data, regex): else: for i in range(0, len(item)): - found = True + found = True name = "group_%d" % i try: returnvalues[name].append(item[i]) @@ -558,10 +566,7 @@ def regex_capture_group(self, input_data, regex): "reason": "Bad regex pattern: %s" % e, } - def regex_replace( - self, input_data, regex, replace_string="", ignore_case="False" - ): - + def regex_replace(self, input_data, regex, replace_string="", ignore_case="False"): if ignore_case.lower().strip() == "true": return re.sub(regex, replace_string, input_data, flags=re.IGNORECASE) else: @@ -587,10 +592,11 @@ def execute_python(self, code): # 2. Subprocess execute file? try: f = StringIO() + def custom_print(*args, **kwargs): return print(*args, file=f, **kwargs) - - #with redirect_stdout(f): # just in case + + # with redirect_stdout(f): # just in case # Add globals in it too globals_copy = globals().copy() globals_copy["print"] = custom_print @@ -600,7 +606,6 @@ def custom_print(*args, **kwargs): except Exception as e: self.logger.info(f"Failed to add singul to python globals: {e}") - # Add self to globals_copy for key, value in locals().copy().items(): if key not in globals_copy: @@ -614,7 +619,7 @@ def custom_print(*args, **kwargs): # Same as a return pass except SyntaxError as e: - # Special handler for return usage. Makes return act as + # Special handler for return usage. Makes return act as # an exit() if "'return' outside function" in str(e): return { @@ -631,11 +636,11 @@ def custom_print(*args, **kwargs): # Reason: SyntaxError makes it crash BEFORE it reaches the return s = f.getvalue() - f.close() # why: https://www.youtube.com/watch?v=6SA6S9Ca5-U + f.close() # why: https://www.youtube.com/watch?v=6SA6S9Ca5-U - #try: + # try: # s = s.encode("utf-8") - #except Exception as e: + # except Exception as e: try: return { @@ -653,7 +658,7 @@ def custom_print(*args, **kwargs): "success": True, "message": s, } - + except Exception as e: return { "success": False, @@ -726,7 +731,6 @@ def preload_cache(self, key): response_data["value"] = parsed return get_response.json() - def update_cache(self, key): org_id = self.full_execution["workflow"]["execution_org"]["id"] url = f"{self.url}/api/v1/orgs/{org_id}/set_cache" @@ -743,9 +747,7 @@ def update_cache(self, key): self.cache_update_buffer = [] return get_response.json() - def filter_list(self, input_list, field, check, value, opposite): - # Remove hashtags on the fly # E.g. #.fieldname or .#.fieldname @@ -754,20 +756,22 @@ def filter_list(self, input_list, field, check, value, opposite): flip = True try: - #input_list = eval(input_list) # nosec + # input_list = eval(input_list) # nosec input_list = json.loads(input_list) except Exception: try: input_list = input_list.replace("'", '"', -1) input_list = json.loads(input_list) except Exception: - self.logger.info("[WARNING] Error parsing string to array. Continuing anyway.") + self.logger.info( + "[WARNING] Error parsing string to array. Continuing anyway." + ) # Workaround D: if not isinstance(input_list, list): return { "success": False, - "reason": "Error: input isnt a list. Please use conditions instead if using JSON.", + "reason": "Error: input isnt a list. Please use conditions instead if using JSON.", "valid": [], "invalid": [], } @@ -799,7 +803,6 @@ def filter_list(self, input_list, field, check, value, opposite): except json.decoder.JSONDecodeError as e: pass - # EQUALS JUST FOR STR if check == "equals": # Mostly for bools @@ -817,7 +820,7 @@ def filter_list(self, input_list, field, check, value, opposite): for subcheck in checklist: subcheck = str(subcheck).strip() - #ext.lower().strip() == value.lower().strip() + # ext.lower().strip() == value.lower().strip() if type(tmp) == list and subcheck in tmp: new_list.append(item) found = True @@ -874,8 +877,10 @@ def filter_list(self, input_list, field, check, value, opposite): # CONTAINS FIND FOR LIST AND IN FOR STR elif check == "contains": - #if str(value).lower() in str(tmp).lower(): - if str(value).lower() in str(tmp).lower() or self.check_wildcard(value, tmp): + # if str(value).lower() in str(tmp).lower(): + if str(value).lower() in str(tmp).lower() or self.check_wildcard( + value, tmp + ): new_list.append(item) else: failed_list.append(item) @@ -885,7 +890,9 @@ def filter_list(self, input_list, field, check, value, opposite): checklist = value.split(",") found = False for checker in checklist: - if str(checker).lower() in str(tmp).lower() or self.check_wildcard(checker, tmp): + if str(checker).lower() in str( + tmp + ).lower() or self.check_wildcard(checker, tmp): new_list.append(item) found = True break @@ -929,9 +936,9 @@ def filter_list(self, input_list, field, check, value, opposite): failed_list.append(item) elif check == "less than": # Old - #if int(tmp) < int(value): + # if int(tmp) < int(value): # new_list.append(item) - #else: + # else: # failed_list.append(item) list_set = False @@ -970,16 +977,17 @@ def filter_list(self, input_list, field, check, value, opposite): else: failed_list.append(item) - if len(self.cache_update_buffer) > 400 or (item == input_list[-1] and len(self.cache_update_buffer) > 0): + if len(self.cache_update_buffer) > 400 or ( + item == input_list[-1] and len(self.cache_update_buffer) > 0 + ): self.update_cache(value) - - #return { + # return { # "success": True, # "found": False, # "key": key, # "value": new_value, - #} + # } # SINGLE ITEM COULD BE A FILE OR A LIST OF FILES elif check == "files by extension": @@ -989,7 +997,7 @@ def filter_list(self, input_list, field, check, value, opposite): for file_id in tmp: filedata = self.get_file(file_id) _, ext = os.path.splitext(filedata["filename"]) - if (ext.lower().strip() == value.lower().strip()): + if ext.lower().strip() == value.lower().strip(): file_list.append(file_id) # else: # failed_list.append(file_id) @@ -1023,11 +1031,11 @@ def filter_list(self, input_list, field, check, value, opposite): failed_list = tmplist try: - data ={ - "success": True, - "valid": new_list, - "invalid": failed_list, - } + data = { + "success": True, + "valid": new_list, + "invalid": failed_list, + } return json.dumps(data) # new_list = json.dumps(new_list) @@ -1041,7 +1049,7 @@ def filter_list(self, input_list, field, check, value, opposite): return new_list - #def multi_list_filter(self, input_list, field, check, value): + # def multi_list_filter(self, input_list, field, check, value): # input_list = input_list.replace("'", '"', -1) # input_list = json.loads(input_list) @@ -1129,13 +1137,13 @@ def create_file(self, filename, data, category=""): try: if str(data).startswith("b'") and str(data).endswith("'"): data = data[2:-1] - if str(data).startswith("\"") and str(data).endswith("\""): + if str(data).startswith('"') and str(data).endswith('"'): data = data[2:-1] except Exception as e: self.logger.info(f"Exception: {e}") try: - #if not isinstance(data, str) and not isinstance(data, int) and not isinstance(float) and not isinstance(data, bool): + # if not isinstance(data, str) and not isinstance(data, int) and not isinstance(float) and not isinstance(data, bool): if isinstance(data, dict) or isinstance(data, list): data = json.dumps(data) except: @@ -1152,13 +1160,13 @@ def create_file(self, filename, data, category=""): if len(fileret) == 1: value = {"success": True, "filename": filename, "file_id": fileret[0]} - return value + return value - # Input is WAS a file, hence it didn't get the files + # Input is WAS a file, hence it didn't get the files def list_file_category_ids(self, file_category): return self.get_file_category_ids(file_category) - # Input is WAS a file, hence it didn't get the files + # Input is WAS a file, hence it didn't get the files # Category doesn't matter as it uses file ID, which is unique anyway def get_file_value(self, filedata, category=""): filedata = self.get_file(filedata, category) @@ -1218,7 +1226,6 @@ def download_remote_file(self, url, custom_filename="", category=""): return value - def extract_archive(self, file_id, fileformat="zip", password=None): try: return_data = {"success": False, "files": []} @@ -1227,7 +1234,6 @@ def extract_archive(self, file_id, fileformat="zip", password=None): return_ids = None with tempfile.TemporaryDirectory() as tmpdirname: - # Get archive and save phisically with open(os.path.join(tmpdirname, "archive"), "wb") as f: f.write(item["data"]) @@ -1237,7 +1243,9 @@ def extract_archive(self, file_id, fileformat="zip", password=None): # Zipfile for zipped archive if fileformat.strip().lower() == "zip": try: - with zipfile.ZipFile(os.path.join(tmpdirname, "archive")) as z_file: + with zipfile.ZipFile( + os.path.join(tmpdirname, "archive") + ) as z_file: if password: z_file.setpassword(bytes(password.encode())) @@ -1248,7 +1256,10 @@ def extract_archive(self, file_id, fileformat="zip", password=None): source = z_file.open(member) to_be_uploaded.append( - {"filename": source.name.split("/")[-1], "data": source.read()} + { + "filename": source.name.split("/")[-1], + "data": source.read(), + } ) return_data["success"] = True @@ -1276,7 +1287,10 @@ def extract_archive(self, file_id, fileformat="zip", password=None): source = z_file.open(member) to_be_uploaded.append( - {"filename": source.name.split("/")[-1], "data": source.read()} + { + "filename": source.name.split("/")[-1], + "data": source.read(), + } ) return_data["success"] = True @@ -1319,7 +1333,9 @@ def extract_archive(self, file_id, fileformat="zip", password=None): ) elif fileformat.strip().lower() == "tar.gz": try: - with tarfile.open(os.path.join(tmpdirname, "archive"), mode="r:gz") as z_file: + with tarfile.open( + os.path.join(tmpdirname, "archive"), mode="r:gz" + ) as z_file: for member in z_file.getnames(): member_files = z_file.extractfile(member) @@ -1444,22 +1460,23 @@ def create_archive(self, file_ids, fileformat, name, password=None): # Create archive temporary with tempfile.NamedTemporaryFile() as archive: - if fileformat == "zip": archive_name = "archive.zip" if not name else name - pwd = password if isinstance(password, (bytes, bytearray)) else password.encode() + pwd = ( + password + if isinstance(password, (bytes, bytearray)) + else password.encode() + ) with pyzipper.AESZipFile( - archive.name, - "w", - compression=pyzipper.ZIP_DEFLATED - ) as zf: - zf.setpassword(pwd) - zf.setencryption(pyzipper.WZ_AES, nbits=256) + archive.name, "w", compression=pyzipper.ZIP_DEFLATED + ) as zf: + zf.setpassword(pwd) + zf.setencryption(pyzipper.WZ_AES, nbits=256) - for path in paths: - zf.write(path, arcname=os.path.basename(path)) + for path in paths: + zf.write(path, arcname=os.path.basename(path)) elif fileformat == "7zip": archive_name = "archive.7z" if not name else name @@ -1491,8 +1508,13 @@ def create_archive(self, file_ids, fileformat, name, password=None): return {"success": False, "message": excp} def add_list_to_list(self, list_one, list_two): - if not isinstance(list_one, list) and not isinstance(list_one, dict): - if not list_one or list_one == " " or list_one == "None" or list_one == "null": + if not isinstance(list_one, list) and not isinstance(list_one, dict): + if ( + not list_one + or list_one == " " + or list_one == "None" + or list_one == "null" + ): list_one = "[]" try: @@ -1503,11 +1525,16 @@ def add_list_to_list(self, list_one, list_two): else: return { "success": False, - "reason": f"List one is not a valid list: {list_one}" + "reason": f"List one is not a valid list: {list_one}", } if not isinstance(list_two, list) and not isinstance(list_two, dict): - if not list_two or list_two == " " or list_two == "None" or list_two == "null": + if ( + not list_two + or list_two == " " + or list_two == "None" + or list_two == "null" + ): list_two = "[]" try: @@ -1518,7 +1545,7 @@ def add_list_to_list(self, list_one, list_two): else: return { "success": False, - "reason": f"List two is not a valid list: {list_two}" + "reason": f"List two is not a valid list: {list_two}", } if isinstance(list_one, dict): @@ -1536,19 +1563,13 @@ def diff_lists(self, list_one, list_two): try: list_one = json.loads(list_one) except json.decoder.JSONDecodeError as e: - return { - "success": False, - "reason": "list_one is not a valid list." - } + return {"success": False, "reason": "list_one is not a valid list."} if isinstance(list_two, str): try: list_two = json.loads(list_two) except json.decoder.JSONDecodeError as e: - return { - "success": False, - "reason": "list_two is not a valid list." - } + return {"success": False, "reason": "list_two is not a valid list."} def diff(li1, li2): try: @@ -1557,7 +1578,7 @@ def diff(li1, li2): # Bad json diffing - at least order doesn't matter :) not_found = [] for item in list_one: - #item = sorted(item.items()) + # item = sorted(item.items()) if item in list_two: pass else: @@ -1585,8 +1606,14 @@ def diff(li1, li2): "diff": newdiff, } - - def merge_lists(self, list_one, list_two, set_field="", sort_key_list_one="", sort_key_list_two=""): + def merge_lists( + self, + list_one, + list_two, + set_field="", + sort_key_list_one="", + sort_key_list_two="", + ): if isinstance(list_one, str): try: list_one = json.loads(list_one) @@ -1603,23 +1630,34 @@ def merge_lists(self, list_one, list_two, set_field="", sort_key_list_one="", so if isinstance(list_one, dict) and isinstance(list_two, dict): for key, value in list_two.items(): list_one[key] = value - + return list_one - return {"success": False, "message": "Both input lists need to be valid JSON lists."} + return { + "success": False, + "message": "Both input lists need to be valid JSON lists.", + } if len(list_one) != len(list_two): - return {"success": False, "message": "Lists length must be the same. %d vs %d. Are you trying to add them to a single list? Use add_list_to_list" % (len(list_one), len(list_two))} + return { + "success": False, + "message": "Lists length must be the same. %d vs %d. Are you trying to add them to a single list? Use add_list_to_list" + % (len(list_one), len(list_two)), + } if len(sort_key_list_one) > 0: try: - list_one = sorted(list_one, key=lambda k: k.get(sort_key_list_one), reverse=True) + list_one = sorted( + list_one, key=lambda k: k.get(sort_key_list_one), reverse=True + ) except: pass if len(sort_key_list_two) > 0: try: - list_two = sorted(list_two, key=lambda k: k.get(sort_key_list_two), reverse=True) + list_two = sorted( + list_two, key=lambda k: k.get(sort_key_list_two), reverse=True + ) except: pass @@ -1633,7 +1671,11 @@ def merge_lists(self, list_one, list_two, set_field="", sort_key_list_one="", so list_one[i][key] = value elif isinstance(list_two[i], str) and list_two[i] == "": continue - elif isinstance(list_two[i], str) or isinstance(list_two[i], int) or isinstance(list_two[i], bool): + elif ( + isinstance(list_two[i], str) + or isinstance(list_two[i], int) + or isinstance(list_two[i], bool) + ): if len(set_field) == 0: list_one[i][base_key] = list_two[i] else: @@ -1648,8 +1690,21 @@ def merge_lists(self, list_one, list_two, set_field="", sort_key_list_one="", so return list_one - def merge_json_objects(self, list_one, list_two, set_field="", sort_key_list_one="", sort_key_list_two=""): - return self.merge_lists(list_one, list_two, set_field=set_field, sort_key_list_one=sort_key_list_one, sort_key_list_two=sort_key_list_two) + def merge_json_objects( + self, + list_one, + list_two, + set_field="", + sort_key_list_one="", + sort_key_list_two="", + ): + return self.merge_lists( + list_one, + list_two, + set_field=set_field, + sort_key_list_one=sort_key_list_one, + sort_key_list_two=sort_key_list_two, + ) def fix_json(self, json_data): try: @@ -1670,14 +1725,14 @@ def fix_json(self, json_data): else: json_data[key] = value - #elif isinstance(value, list): + # elif isinstance(value, list): # json_data[key] = value - #else: + # else: # json_data[key] = value # #for item in json_data[key]: # # if isinstance(item, dict): # # json_data[ - + for key in deletekeys: del json_data[key] @@ -1695,7 +1750,7 @@ def xml_json_convertor(self, convertto, data): try: if convertto == "json": - data = data.replace(" encoding=\"utf-8\"", " ") + data = data.replace(' encoding="utf-8"', " ") ans = xmltodict.parse(data) ans = self.fix_json(ans) json_data = json.dumps(ans) @@ -1705,11 +1760,7 @@ def xml_json_convertor(self, convertto, data): ans = readfromstring(data) return json2xml.Json2xml(ans, wrapper="all", pretty=True).to_xml() except Exception as e: - return { - "success": False, - "input": data, - "reason": f"{e}" - } + return {"success": False, "input": data, "reason": f"{e}"} def date_to_epoch(self, input_data, date_field, date_format): if isinstance(input_data, str): @@ -1727,9 +1778,9 @@ def date_to_epoch(self, input_data, date_field, date_format): def compare_relative_date( self, timestamp, date_format, equality_test, offset, units, direction ): - if timestamp== "None": + if timestamp == "None": return False - + if date_format == "autodetect": input_dt = dateutil_parser(timestamp).replace(tzinfo=None) elif date_format != "%s": @@ -1751,7 +1802,7 @@ def compare_relative_date( if utc_format.endswith("%z"): utc_format = utc_format.replace("%z", "Z") - #if date_format != "%s" and date_format != "autodetect": + # if date_format != "%s" and date_format != "autodetect": if date_format == "autodetect": formatted_dt = datetime.datetime.utcnow() + delta elif date_format != "%s": @@ -1766,24 +1817,24 @@ def compare_relative_date( comparison_dt = formatted_dt elif direction == "ago": comparison_dt = formatted_dt - delta - #formatted_dt - delta - #comparison_dt = datetime.datetime.utcnow() + # formatted_dt - delta + # comparison_dt = datetime.datetime.utcnow() else: comparison_dt = formatted_dt + delta - #comparison_dt = datetime.datetime.utcnow() + # comparison_dt = datetime.datetime.utcnow() diff = int((input_dt - comparison_dt).total_seconds()) if units == "seconds": diff = diff elif units == "minutes": - diff = int(diff/60) + diff = int(diff / 60) elif units == "hours": - diff = int(diff/3600) + diff = int(diff / 3600) elif units == "days": - diff = int(diff/86400) + diff = int(diff / 86400) elif units == "week": - diff = int(diff/604800) + diff = int(diff / 604800) result = False if equality_test == ">": @@ -1797,7 +1848,7 @@ def compare_relative_date( result = not (result) elif equality_test == "=": - result = diff == 0 + result = diff == 0 elif equality_test == "!=": result = diff != 0 @@ -1813,7 +1864,7 @@ def compare_relative_date( parsed_string = "%s %s %s %s" % (equality_test, offset, units, direction) newdiff = diff if newdiff < 0: - newdiff = newdiff*-1 + newdiff = newdiff * -1 return { "success": True, @@ -1821,11 +1872,10 @@ def compare_relative_date( "check": parsed_string, "result": result, "diff": { - "days": int(int(newdiff)/86400), + "days": int(int(newdiff) / 86400), }, } - def run_math_operation(self, operation): result = eval(operation) return result @@ -1840,10 +1890,14 @@ def escape_html(self, input_data): result = markupsafe.escape(mapping) return mapping - def check_datastore_contains(self, key, value, append, category=""): - return check_cache_contains(self, key, value, append, category) + def check_datastore_contains( + self, key, value, append, category="", return_values="true" + ): + return check_cache_contains(self, key, value, append, category, return_values) - def check_cache_contains(self, key, value, append, category=""): + def check_cache_contains( + self, key, value, append, category="", return_values="true" + ): org_id = self.full_execution["workflow"]["execution_org"]["id"] url = "%s/api/v1/orgs/%s/get_cache" % (self.url, org_id) data = { @@ -1862,7 +1916,10 @@ def check_cache_contains(self, key, value, append, category=""): allvalues = {} try: for item in self.local_storage: - if item["execution_id"] == self.current_execution_id and item["key"] == key: + if ( + item["execution_id"] == self.current_execution_id + and item["key"] == key + ): # Max keeping the local cache properly for 5 seconds due to workflow continuations elapsed_time = time.time() - item["time_set"] if elapsed_time > 5: @@ -1871,7 +1928,10 @@ def check_cache_contains(self, key, value, append, category=""): allvalues = item["data"] except Exception as e: - print("[ERROR] Failed cache contains for current execution id local storage: %s" % e) + print( + "[ERROR] Failed cache contains for current execution id local storage: %s" + % e + ) if isinstance(value, dict) or isinstance(value, list): try: @@ -1887,15 +1947,17 @@ def check_cache_contains(self, key, value, append, category=""): if str(append).lower() == "true": append = True else: - append = False + append = False + + include_values = str(return_values).lower() == "true" if "success" not in allvalues: - #get_response = requests.post(url, json=data, verify=False) + # get_response = requests.post(url, json=data, verify=False) pass try: if "success" not in allvalues: - #allvalues = get_response.json() + # allvalues = get_response.json() allvalues = self.shared_cache if "success" not in allvalues: @@ -1925,20 +1987,21 @@ def check_cache_contains(self, key, value, append, category=""): allvalues = set_response.json() self.shared_cache = self.preload_cache(key=key) - newvalue = data["value"] try: newvalue = json.loads(data["value"]) except json.JSONDecodeError: pass - return { + response = { "success": True, "found": False, "key": key, "search": value, - "value": newvalue, } + if include_values: + response["value"] = newvalue + return response except Exception as e: return { "success": False, @@ -1976,8 +2039,8 @@ def check_cache_contains(self, key, value, append, category=""): try: for item in parsedvalue: - #return "%s %s" % (item, value) - #self.logger.info(f"{item} == {value}") + # return "%s %s" % (item, value) + # self.logger.info(f"{item} == {value}") if str(item) == str(value): if not append: try: @@ -1986,25 +2049,31 @@ def check_cache_contains(self, key, value, append, category=""): newdata["data"] = allvalues self.local_storage.append(newdata) except Exception as e: - print("[ERROR] Failed in local storage append: %s" % e) + print( + "[ERROR] Failed in local storage append: %s" % e + ) - return { + response = { "success": True, "found": True, "reason": "Found and not appending!", "key": key, "search": value, - "value": allvalues["value"], } + if include_values: + response["value"] = allvalues["value"] + return response else: - return { + response = { "success": True, "found": True, "reason": "Found, was appending, but item already exists", "key": key, "search": value, - "value": allvalues["value"], } + if include_values: + response["value"] = allvalues["value"] + return response # Lol break @@ -2013,20 +2082,24 @@ def check_cache_contains(self, key, value, append, category=""): append = True if not append: - return { + response = { "success": True, "found": False, "reason": "Not found, not appending (2)!", "key": key, "search": value, - "value": allvalues["value"], } + if include_values: + response["value"] = allvalues["value"] + return response - #parsedvalue.append(value) + # parsedvalue.append(value) - #data["value"] = json.dumps(parsedvalue) + # data["value"] = json.dumps(parsedvalue) - if value not in allvalues["value"] and isinstance(allvalues["value"], list): + if value not in allvalues["value"] and isinstance( + allvalues["value"], list + ): self.cache_update_buffer.append(value) allvalues["value"].append(value) @@ -2052,14 +2125,16 @@ def check_cache_contains(self, key, value, append, category=""): except: pass - return { + response = { "success": True, "found": False, - "reason": f"Appended as it didn't exist", + "reason": "Appended as it didn't exist", "key": key, "search": value, - "value": newvalue, } + if include_values: + response["value"] = newvalue + return response except Exception as e: exception = e pass @@ -2069,10 +2144,10 @@ def check_cache_contains(self, key, value, append, category=""): "found": True, "reason": f"Failed to set append the value: {exception}. This should never happen", "search": value, - "key": key + "key": key, } - #return allvalues + # return allvalues except Exception as e: print("[ERROR] Failed check cache contains: %s" % e) @@ -2084,9 +2159,8 @@ def check_cache_contains(self, key, value, append, category=""): "found": False, } - return value.text + return value.text - ## Adds value to a subkey of the cache ## subkey = "hi", value = "test", overwrite=False ## {"subkey": "hi", "value": "test"} @@ -2124,13 +2198,17 @@ def change_cache_subkey(self, key, subkey, value, overwrite, category=""): try: allvalues = response.json() allvalues["key"] = key - #allvalues["value"] = json.loads(json.dumps(value)) + # allvalues["value"] = json.loads(json.dumps(value)) - if (value.startswith("{") and value.endswith("}")) or (value.startswith("[") and value.endswith("]")): + if (value.startswith("{") and value.endswith("}")) or ( + value.startswith("[") and value.endswith("]") + ): try: allvalues["value"] = json.loads(value) except json.decoder.JSONDecodeError as e: - self.logger.info("[WARNING] Failed inner value cache parsing: %s" % e) + self.logger.info( + "[WARNING] Failed inner value cache parsing: %s" % e + ) allvalues["value"] = str(value) else: allvalues["value"] = str(value) @@ -2151,7 +2229,18 @@ def get_datastore_value(self, key, category=""): def get_ioc(self, ioc, data_type=""): if len(data_type) == 0: - ioc_types = ["domains", "urls", "email_addresses", "ipv4s", "ipv6s", "ipv4_cidrs", "md5s", "sha256s", "sha1s", "cves"] + ioc_types = [ + "domains", + "urls", + "email_addresses", + "ipv4s", + "ipv6s", + "ipv4_cidrs", + "md5s", + "sha256s", + "sha1s", + "cves", + ] iocs = find_iocs(str(ioc)) for key, value in iocs.items(): @@ -2183,8 +2272,8 @@ def get_ioc(self, ioc, data_type=""): if allvalues["success"] == True and len(allvalues["value"]) > 0: allvalues["found"] = True else: - allvalues["success"] = True - allvalues["found"] = False + allvalues["success"] = True + allvalues["found"] = False try: parsedvalue = json.loads(allvalues["value"]) @@ -2220,8 +2309,8 @@ def get_cache_value(self, key, category=""): if allvalues["success"] == True and len(allvalues["value"]) > 0: allvalues["found"] = True else: - allvalues["success"] = True - allvalues["found"] = False + allvalues["success"] = True + allvalues["found"] = False try: parsedvalue = json.loads(allvalues["value"]) @@ -2260,9 +2349,11 @@ def search_datastore_category(self, input_list, key, category): try: input_list = json.loads(str(input_list)) except Exception as e: - returnvalue["reason"] = f"Input list is not a valid JSON list: {input_list}", + returnvalue["reason"] = ( + f"Input list is not a valid JSON list: {input_list}", + ) returnvalue["details"] = str(e) - return returnvalue + return returnvalue org_id = self.full_execution["workflow"]["execution_org"]["id"] cnt = -1 @@ -2272,23 +2363,31 @@ def search_datastore_category(self, input_list, key, category): try: item = json.loads(str(item)) except Exception as e: - self.logger.info("[ERROR][%s] Failed to parse item as JSON: %s" % (self.current_execution_id, e)) + self.logger.info( + "[ERROR][%s] Failed to parse item as JSON: %s" + % (self.current_execution_id, e) + ) continue input_list[cnt] = item if key not in item: - returnvalue["reason"] = "Couldn't find key '%s' in every item. Make sure to use a key that exists in every entry." % (key), + returnvalue["reason"] = ( + "Couldn't find key '%s' in every item. Make sure to use a key that exists in every entry." + % (key), + ) return returnvalue - data.append({ - "workflow_id": self.full_execution["workflow"]["id"], - "execution_id": self.current_execution_id, - "authorization": self.authorization, - "org_id": org_id, - "key": str(item[key]), - "value": json.dumps(item), - "category": category, - }) + data.append( + { + "workflow_id": self.full_execution["workflow"]["id"], + "execution_id": self.current_execution_id, + "authorization": self.authorization, + "org_id": org_id, + "key": str(item[key]), + "value": json.dumps(item), + "category": category, + } + ) url = f"{self.url}/api/v2/datastore?bulk=true&execution_id={self.current_execution_id}&authorization={self.authorization}" response = requests.post(url, json=data, verify=False) @@ -2300,16 +2399,18 @@ def search_datastore_category(self, input_list, key, category): return returnvalue data = "" - try: + try: data = response.json() except json.decoder.JSONDecodeError as e: - return response.text + return response.text if "keys_existed" not in data: - returnvalue["error"] = "Invalid response from backend during bulk update of keys" + returnvalue["error"] = ( + "Invalid response from backend during bulk update of keys" + ) returnvalue["details"] = data - return returnvalue + return returnvalue not_found_keys = [] returnvalue["success"] = True @@ -2324,17 +2425,22 @@ def search_datastore_category(self, input_list, key, category): else: returnvalue["new"].append(datastore_item) - found = True + found = True break if not found: - print("[ERROR][%s] Key %s not found in datastore response, adding as new" % (self.current_execution_id, datastore_item[key])) - #returnvalue["new"].append(datastore_item) + print( + "[ERROR][%s] Key %s not found in datastore response, adding as new" + % (self.current_execution_id, datastore_item[key]) + ) + # returnvalue["new"].append(datastore_item) not_found_keys.append(datastore_item[key]) if len(not_found_keys) > 0: returnvalue["unhandled_keys"] = not_found_keys - returnvalue["reason"] = "Something went wrong updating the unhandled_keys. Please contact support@shuffler.io if this persists." + returnvalue["reason"] = ( + "Something went wrong updating the unhandled_keys. Please contact support@shuffler.io if this persists." + ) return json.dumps(returnvalue, indent=4) @@ -2347,7 +2453,7 @@ def set_cache_value(self, key, value, category=""): value = json.dumps(value) except Exception as e: self.logger.info(f"[WARNING] Error in JSON dumping (set cache): {e}") - + if not isinstance(value, str): value = str(value) @@ -2367,20 +2473,24 @@ def set_cache_value(self, key, value, category=""): try: allvalues = response.json() allvalues["key"] = key - #allvalues["value"] = json.loads(json.dumps(value)) + # allvalues["value"] = json.loads(json.dumps(value)) - allvalues["existed"] = False + allvalues["existed"] = False if "keys_existed" in allvalues: for key_info in allvalues["keys_existed"]: if key_info["key"] == key: allvalues["existed"] = key_info["existed"] break - if (value.startswith("{") and value.endswith("}")) or (value.startswith("[") and value.endswith("]")): + if (value.startswith("{") and value.endswith("}")) or ( + value.startswith("[") and value.endswith("]") + ): try: allvalues["value"] = json.loads(value) except json.decoder.JSONDecodeError as e: - self.logger.info("[WARNING] Failed inner value cache parsing: %s" % e) + self.logger.info( + "[WARNING] Failed inner value cache parsing: %s" % e + ) allvalues["value"] = str(value) else: allvalues["value"] = str(value) @@ -2393,12 +2503,17 @@ def set_cache_value(self, key, value, category=""): self.logger.info("Value couldn't be parsed") return response.text - def convert_json_to_tags(self, json_object, split_value=", ", include_key=True, lowercase=True): + def convert_json_to_tags( + self, json_object, split_value=", ", include_key=True, lowercase=True + ): if isinstance(json_object, str): try: json_object = json.loads(json_object) except json.decoder.JSONDecodeError as e: - self.logger.info("Failed to parse list2 as json: %s. Type: %s" % (e, type(json_object))) + self.logger.info( + "Failed to parse list2 as json: %s. Type: %s" + % (e, type(json_object)) + ) if isinstance(lowercase, str) and lowercase.lower() == "true": lowercase = True @@ -2413,7 +2528,11 @@ def convert_json_to_tags(self, json_object, split_value=", ", include_key=True, parsedstring = [] try: for key, value in json_object.items(): - if isinstance(value, str) or isinstance(value, int) or isinstance(value, bool): + if ( + isinstance(value, str) + or isinstance(value, int) + or isinstance(value, bool) + ): if include_key == True: parsedstring.append("%s:%s" % (key, value)) else: @@ -2462,7 +2581,7 @@ def cidr_ip_match(self, ip, networks): try: ip_networks = list(map(ipaddress.ip_network, networks)) - #ip_address = ipaddress.ip_address(ip, False) + # ip_address = ipaddress.ip_address(ip, False) ip_address = ipaddress.ip_address(ip) except ValueError as e: return "IP or some networks are not in valid format.\nError: {}".format(e) @@ -2471,8 +2590,8 @@ def cidr_ip_match(self, ip, networks): result = {} result["ip"] = ip - result['networks'] = list(map(str, matched_networks)) - result['is_contained'] = True if len(result['networks']) > 0 else False + result["networks"] = list(map(str, matched_networks)) + result["is_contained"] = True if len(result["networks"]) > 0 else False return json.dumps(result) @@ -2488,12 +2607,12 @@ def get_hash_sum(self, value): sha256_value = "" try: - md5_value = hashlib.md5(str(value).encode('utf-8')).hexdigest() + md5_value = hashlib.md5(str(value).encode("utf-8")).hexdigest() except Exception as e: pass try: - sha256_value = hashlib.sha256(str(value).encode('utf-8')).hexdigest() + sha256_value = hashlib.sha256(str(value).encode("utf-8")).hexdigest() except Exception as e: pass @@ -2504,14 +2623,17 @@ def get_hash_sum(self, value): "sha256": sha256_value, } - return parsedvalue + return parsedvalue def run_oauth_request(self, url, jwt): headers = { "Content-Type": "application/x-www-form-urlencoded", } - data = "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=%s" % jwt + data = ( + "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=%s" + % jwt + ) return requests.post(url, data=data, headers=headers, verify=False).text @@ -2519,10 +2641,9 @@ def run_oauth_request(self, url, jwt): def get_jwt_from_file(self, file_id, jwt_audience, scopes, complete_request=True): allscopes = scopes - if "," in scopes: allscopes = " ".join(scopes.split(",")) - + # Service account key path filedata = self.get_file(file_id) if filedata["success"] == False: @@ -2530,49 +2651,43 @@ def get_jwt_from_file(self, file_id, jwt_audience, scopes, complete_request=True "success": False, "message": f"Failed to get file for ID {file_id}", } - + data = json.loads(filedata["data"], strict=False) - #sa_keyfile = "" + # sa_keyfile = "" sa_keyfile = data["private_key"] sa_email = data["client_email"] - + # The audience to target audience = jwt_audience - + """Generates a signed JSON Web Token using a Google API Service Account or similar.""" - def get_jwt(sa_keyfile, - sa_email, - audience, - allscopes, - expiry_length=3600): - + + def get_jwt(sa_keyfile, sa_email, audience, allscopes, expiry_length=3600): now = int(time.time()) - + # build payload payload = { # expires after 'expiry_length' seconds. # iss must match 'issuer' in the security configuration in your # swagger spec (e.g. service account email). It can be any string. - 'iss': sa_email, + "iss": sa_email, # aud must be either your Endpoints service name, or match the value # specified as the 'x-google-audience' in the OpenAPI document. - 'scope': allscopes, - 'aud': audience, + "scope": allscopes, + "aud": audience, "exp": now + expiry_length, - 'iat': now, - + "iat": now, # sub and email should match the service account's email address - 'sub': sa_email, - 'email': sa_email, + "sub": sa_email, + "email": sa_email, } - + # sign with keyfile - #signer = crypt.RSASigner.from_service_account_file(sa_keyfile) + # signer = crypt.RSASigner.from_service_account_file(sa_keyfile) signer = crypt.RSASigner.from_string(sa_keyfile) jwt_token = jwt.encode(signer, payload) return jwt_token - - + signed_jwt = get_jwt(sa_keyfile, sa_email, audience, allscopes) if str(complete_request).lower() == "true": @@ -2601,11 +2716,18 @@ def get_synonyms(self, input_type): "uuid", "teamid", "messageid", - ], - "title": ["title", "message", "subject", "name"], - "description": ["description", "status", "explanation", "story", "details", "snippet"], - "email": ["mail", "email", "sender", "receiver", "recipient"], - "data": [ + ], + "title": ["title", "message", "subject", "name"], + "description": [ + "description", + "status", + "explanation", + "story", + "details", + "snippet", + ], + "email": ["mail", "email", "sender", "receiver", "recipient"], + "data": [ "data", "ip", "domain", @@ -2617,9 +2739,9 @@ def get_synonyms(self, input_type): "value", "item", "rules", - ], - "tags": ["tags", "taxonomies", "labels", "labelids"], - "assignment": [ + ], + "tags": ["tags", "taxonomies", "labels", "labelids"], + "assignment": [ "assignment", "user", "assigned_to", @@ -2627,40 +2749,44 @@ def get_synonyms(self, input_type): "closed_by", "closing_user", "opened_by", - ], - "severity": [ + ], + "severity": [ "severity", "sev", "magnitude", "relevance", - ] + ], } - + return [] - + def find_key(self, inputkey, synonyms): inputkey = inputkey.lower().replace(" ", "").replace(".", "") for key, value in synonyms.items(): if inputkey in value: return key - + return inputkey - + def run_key_recursion(self, json_input, synonyms): - if isinstance(json_input, str) or isinstance(json_input, int) or isinstance(json_input, float): + if ( + isinstance(json_input, str) + or isinstance(json_input, int) + or isinstance(json_input, float) + ): return json_input, {} - + if isinstance(json_input, list): if len(json_input) != 1: return json_input, {} else: json_input = json_input[0] - - #new_list = [] - #for item in json_input: - #run_key_recursion(item, synonyms) - #new_dict[new_key], found_important = run_key_recursion(value, synonyms) - + + # new_list = [] + # for item in json_input: + # run_key_recursion(item, synonyms) + # new_dict[new_key], found_important = run_key_recursion(value, synonyms) + # Looks for exact key:value stuff in other format if len(json_input.keys()) == 2: newkey = "" @@ -2670,54 +2796,58 @@ def run_key_recursion(self, json_input, synonyms): newkey = value elif key == "value": newvalue = value - + if len(newkey) > 0 and len(newvalue) > 0: json_input[newkey] = newvalue try: del json_input["name"] except: pass - + try: del json_input["value"] except: pass - + try: del json_input["key"] except: pass - + important_fields = {} new_dict = {} for key, value in json_input.items(): new_key = self.find_key(key, synonyms) - + if isinstance(value, list): new_list = [] for subitem in value: - returndata, found_important = self.run_key_recursion(subitem, synonyms) - + returndata, found_important = self.run_key_recursion( + subitem, synonyms + ) + new_list.append(returndata) for subkey, subvalue in found_important.items(): - important_fields[subkey] = subvalue - + important_fields[subkey] = subvalue + new_dict[new_key] = new_list - + elif isinstance(value, dict): # FIXMe: Try to understand Key:Values as well by translating them # name/key: subject # value: This is a subject # will become: # subject: This is a subject - - new_dict[new_key], found_important = self.run_key_recursion(value, synonyms) - + + new_dict[new_key], found_important = self.run_key_recursion( + value, synonyms + ) + for subkey, subvalue in found_important.items(): important_fields[subkey] = subvalue else: new_dict[new_key] = value - + # Translated fields are added as important if key.lower().replace(" ", "").replace(".", "") != new_key: try: @@ -2727,31 +2857,33 @@ def run_key_recursion(self, json_input, synonyms): important_fields[new_key] = new_dict[new_key] except: important_fields[new_key] = new_dict[new_key] - - #break - + + # break + return new_dict, important_fields - + # Should translate the data to something more useful def get_standardized_data(self, json_input, input_type): if isinstance(json_input, str): json_input = json.loads(json_input, strict=False) - + input_synonyms = self.get_synonyms(input_type) - parsed_data, important_fields = self.run_key_recursion(json_input, input_synonyms) - + parsed_data, important_fields = self.run_key_recursion( + json_input, input_synonyms + ) + # Try base64 decoding and such too? for key, value in important_fields.items(): try: important_fields[key] = important_fields[key][key] except: pass - + try: important_fields[key] = base64.b64decode(important_fields[key]) except: pass - + return { "success": True, "original": json_input, @@ -2773,14 +2905,16 @@ def generate_random_string(self, length=16, special_characters=True): if str(special_characters).lower() == "false": characters = string.ascii_letters + string.digits + string.punctuation - password = ''.join(random.choice(characters) for i in range(length)) + password = "".join(random.choice(characters) for i in range(length)) return { "success": True, "password": password, } - - def run_ssh_command(self, host, port, user_name, private_key_file_id, password, command): + + def run_ssh_command( + self, host, port, user_name, private_key_file_id, password, command + ): ssh_client = paramiko.SSHClient() ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) @@ -2795,60 +2929,74 @@ def run_ssh_command(self, host, port, user_name, private_key_file_id, password, try: key_data = new_file["data"].decode() except Exception as e: - return {"success":"false","message":str(e)} + return {"success": "false", "message": str(e)} private_key_file = StringIO() private_key_file.write(key_data) private_key_file.seek(0) private_key = paramiko.RSAKey.from_private_key(private_key_file) - + try: - ssh_client.connect(hostname=host,username=user_name,port=port, pkey= private_key) + ssh_client.connect( + hostname=host, username=user_name, port=port, pkey=private_key + ) except Exception as e: - return {"success":"false","message":str(e)} + return {"success": "false", "message": str(e)} else: try: - ssh_client.connect(hostname=host,username=user_name,port=port, password=str(password)) + ssh_client.connect( + hostname=host, username=user_name, port=port, password=str(password) + ) except Exception as e: - return {"success":"false","message":str(e)} + return {"success": "false", "message": str(e)} try: stdin, stdout, stderr = ssh_client.exec_command(str(command)) try: - errorLog = stderr.read().decode(errors='ignore') + errorLog = stderr.read().decode(errors="ignore") except Exception as e: errorLog = f"Failed to read stderr {e}" try: - output = stdout.read().decode(errors='ignore') + output = stdout.read().decode(errors="ignore") except Exception as e: output = f"Failed to read stdout {e}" except Exception as e: - return {"success":"false","message":str(e)} + return {"success": "false", "message": str(e)} - return {"success":"true","output": output, "error_logs": errorLog} + return {"success": "true", "output": output, "error_logs": errorLog} def cleanup_ioc_data(self, input_data): # Remove unecessary parts like { and }, quotes etc input_data = str(input_data) input_data = input_data.replace("{", "") input_data = input_data.replace("}", "") - input_data = input_data.replace("\"", "") + input_data = input_data.replace('"', "") input_data = input_data.replace("'", "") input_data = input_data.replace("\t", " ") input_data = input_data.replace(" ", " ") input_data = input_data.replace("\n\n", "\n") - # Remove html tags - input_data = re.sub(r'<[^>]*>', '', input_data) + # Remove html tags + input_data = re.sub(r"<[^>]*>", "", input_data) return input_data - def parse_ioc(self, input_string, input_type="all"): - ioc_types = ["domains", "urls", "email_addresses", "ipv4s", "ipv6s", "ipv4_cidrs", "md5s", "sha256s", "sha1s", "cves"] - #ioc_types = ["ipv4s"] + ioc_types = [ + "domains", + "urls", + "email_addresses", + "ipv4s", + "ipv6s", + "ipv4_cidrs", + "md5s", + "sha256s", + "sha1s", + "cves", + ] + # ioc_types = ["ipv4s"] try: input_string = self.cleanup_ioc_data(input_string) @@ -2874,21 +3022,24 @@ def parse_ioc(self, input_string, input_type="all"): new_input_types.append(item) - ioc_types = new_input_types + ioc_types = new_input_types if len(ioc_types) == 0: input_type = "all" # Not used for anything after cleanup fixes - max_size = 7500000 - #if len(input_string) > max_size: + max_size = 7500000 + # if len(input_string) > max_size: # input_string = input_string[:max_size] - self.logger.info("[DEBUG] Parsing data of length %d with types %s. Max size: %d" % (len(input_string), ioc_types, max_size)) + self.logger.info( + "[DEBUG] Parsing data of length %d with types %s. Max size: %d" + % (len(input_string), ioc_types, max_size) + ) self.logger.info(f"STRING: {input_string}") - #iocs = find_iocs(str(input_string), included_ioc_types=ioc_types) + # iocs = find_iocs(str(input_string), included_ioc_types=ioc_types) iocs = find_iocs(str(input_string)) - #self.logger.info("[DEBUG] Found %d ioc types" % len(iocs)) + # self.logger.info("[DEBUG] Found %d ioc types" % len(iocs)) newarray = [] for key, value in iocs.items(): @@ -2925,7 +3076,9 @@ def parse_ioc(self, input_string, input_type="all"): elif "ip" in item["data_type"]: item["data_type"] = "ip" try: - item["is_private_ip"] = ipaddress.ip_address(item["data"]).is_private + item["is_private_ip"] = ipaddress.ip_address( + item["data"] + ).is_private except: pass @@ -2935,25 +3088,24 @@ def parse_ioc(self, input_string, input_type="all"): return "Failed to parse IOC's: %s" % e return newarray - def split_text(self, text): # Split text into chunks of 10kb. Add each 10k to array # In case e.g. 1.2.3.4 lands exactly on 20k boundary, it may be useful to overlap here. # (just shitty code to reduce chance of issues) while still going fast arr_one = [] - max_len = 5000 + max_len = 5000 current_string = "" - overlaps = 100 + overlaps = 100 for i in range(0, len(text)): current_string += text[i] if len(current_string) > max_len: # Appending just in case even with overlaps - if len(text) > i+overlaps: - current_string += text[i+1:i+overlaps] + if len(text) > i + overlaps: + current_string += text[i + 1 : i + overlaps] else: - current_string += text[i+1:] + current_string += text[i + 1 :] arr_one.append(current_string) current_string = "" @@ -2961,13 +3113,13 @@ def split_text(self, text): if len(current_string) > 0: arr_one.append(current_string) - return arr_one + return arr_one def _format_result(self, result): final_result = {} - + for res in result: - for key,val in res.items(): + for key, val in res.items(): if key in final_result: if isinstance(val, list) and len(val) > 0: for i in val: @@ -2975,7 +3127,7 @@ def _format_result(self, result): elif isinstance(val, dict): if key in final_result: if isinstance(val, dict): - for k,v in val.items(): + for k, v in val.items(): val[k].append(v) else: final_result[key] = val @@ -2985,36 +3137,49 @@ def _format_result(self, result): # See function for how it works~: parse_ioc_new(..) def _with_concurency(self, array_of_strings, ioc_types): results = [] - #start = time.perf_counter() + # start = time.perf_counter() # Workers dont matter..? - # What can we use instead? + # What can we use instead? workers = 4 with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as executor: # Submit the find_iocs function for each string in the array - futures = [executor.submit( - find_iocs, - text=string, - included_ioc_types=ioc_types, - ) for string in array_of_strings] + futures = [ + executor.submit( + find_iocs, + text=string, + included_ioc_types=ioc_types, + ) + for string in array_of_strings + ] # Wait for all tasks to complete concurrent.futures.wait(futures) # Retrieve the results if needed results = [future.result() for future in futures] - + return self._format_result(results) # FIXME: Make this good and actually faster than normal # For now: Concurrency doesn't make it faster due to GIL in python. - # May need to offload this to an executable or something + # May need to offload this to an executable or something def parse_ioc_new(self, input_string, input_type="all"): if input_type == "": input_type = "all" - ioc_types = ["domains", "urls", "email_addresses", "ipv4s", "ipv4_cidrs", "md5s", "sha256s", "sha1s", "cves"] + ioc_types = [ + "domains", + "urls", + "email_addresses", + "ipv4s", + "ipv4_cidrs", + "md5s", + "sha256s", + "sha1s", + "cves", + ] if input_type == "" or input_type == "all": ioc_types = ioc_types @@ -3028,7 +3193,9 @@ def parse_ioc_new(self, input_string, input_type="all"): input_string = str(input_string) if len(input_string) > 10000: - iocs = self._with_concurency(self.split_text(input_string), ioc_types=ioc_types) + iocs = self._with_concurency( + self.split_text(input_string), ioc_types=ioc_types + ) else: iocs = find_iocs(input_string, included_ioc_types=ioc_types) @@ -3037,7 +3204,7 @@ def parse_ioc_new(self, input_string, input_type="all"): if input_type != "all": if key not in input_type: continue - + if len(value) == 0: continue @@ -3071,7 +3238,9 @@ def parse_ioc_new(self, input_string, input_type="all"): newarray[i]["data_type"] = "ip" try: - newarray[i]["is_private_ip"] = ipaddress.ip_address(item["data"]).is_private + newarray[i]["is_private_ip"] = ipaddress.ip_address( + item["data"] + ).is_private except Exception as e: pass @@ -3085,15 +3254,12 @@ def parse_ioc_new(self, input_string, input_type="all"): def merge_incoming_branches(self, input_type="list"): wf = self.full_execution["workflow"] if "branches" not in wf or not wf["branches"]: - return { - "success": False, - "reason": "No branches found" - } + return {"success": False, "reason": "No branches found"} if "results" not in self.full_execution or not self.full_execution["results"]: return { "success": False, - "reason": "No results for previous actions not found" + "reason": "No results for previous actions not found", } if not input_type: @@ -3101,7 +3267,7 @@ def merge_incoming_branches(self, input_type="list"): branches = wf["branches"] cur_action = self.action - #print("Found %d branches" % len(branches)) + # print("Found %d branches" % len(branches)) results = [] for branch in branches: @@ -3134,13 +3300,13 @@ def merge_incoming_branches(self, input_type="list"): continue newlist.append(subitem) - #newlist.append(item) + # newlist.append(item) results = newlist elif input_type == "dict": new_dict = {} for item in results: - if not isinstance(item, dict): + if not isinstance(item, dict): continue new_dict = self.merge_lists(new_dict, item) @@ -3149,7 +3315,7 @@ def merge_incoming_branches(self, input_type="list"): else: return { "success": False, - "reason": "No results from source branches with type %s" % input_type + "reason": "No results from source branches with type %s" % input_type, } return results @@ -3158,10 +3324,7 @@ def bodyparse_test(self, body): return body def list_cidr_ips(self, cidr): - defaultreturn = { - "success": False, - "reason": "Invalid CIDR address" - } + defaultreturn = {"success": False, "reason": "Invalid CIDR address"} if not cidr: return defaultreturn @@ -3187,11 +3350,7 @@ def list_cidr_ips(self, cidr): return defaultreturn ips = [str(ip) for ip in net] - returnvalue = { - "success": True, - "amount": len(ips), - "ips": ips - } + returnvalue = {"success": True, "amount": len(ips), "ips": ips} return returnvalue @@ -3214,12 +3373,13 @@ def switch(self, conditions): # Loop conditions # Return them without a loop to make it EASY to understand - # Validation should be: + # Validation should be: # Continuation based on .id.valid # .valid -> true/false # If no id exists, use name? return to_return + if __name__ == "__main__": Tools.run() From 295269865cdab35f1be1dda5b3492e2f9ec6fd54 Mon Sep 17 00:00:00 2001 From: Frikky Date: Tue, 10 Feb 2026 12:29:52 +0100 Subject: [PATCH 255/259] Added run_agent action --- shuffle-ai/1.0.0/src/app.py | 105 ++++++++++++++++++++++----- shuffle-tools/1.2.0/requirements.txt | 2 +- shuffle-tools/1.2.0/src/app.py | 4 + 3 files changed, 91 insertions(+), 20 deletions(-) diff --git a/shuffle-ai/1.0.0/src/app.py b/shuffle-ai/1.0.0/src/app.py index 8313ab6e..e5f5e9bc 100644 --- a/shuffle-ai/1.0.0/src/app.py +++ b/shuffle-ai/1.0.0/src/app.py @@ -427,6 +427,45 @@ def gpt(self, input_text): "reason": "Not implemented yet" } + def run_agent(self, input_data, actions=None, apps=None): + prepared_format = { + "id": self.action["id"], + "params": { + "tool_name": self.action["app_name"], + "tool_id": self.action["app_id"], + "environment": self.action["environment"], + "input": { + "text": input_data, + } + }, + } + + if actions: + prepared_format["params"]["tool_name"] = actions + + if apps: + pass + + baseurl = f"{self.url}/api/v1/agent?execution_id={self.current_execution_id}&authorization={self.authorization}&action_id={self.action['id']}" + self.logger.info("[DEBUG] Running agent action with URL '%s'" % (baseurl)) + + headers = {} + request = requests.post( + baseurl, + json=prepared_format, + headers=headers, + ) + + # Random sleep timer to force delay + time.sleep(2) + # Gets into waiting state on backend + return json.dumps({ + "app_run": True, + "input_prompt": prepared_format, + "status": request.status_code, + "body": request.text, + }) + def run_schemaless(self, category, action, app_name="", fields=""): self.logger.info("[DEBUG] Running schemaless action with category '%s' and action label '%s'" % (category, action)) @@ -477,27 +516,55 @@ def run_schemaless(self, category, action, app_name="", fields=""): else: fields = str(fields).strip() - if not fields.startswith("{") and not fields.startswith("["): - fields = json.dumps({ - "data": fields, - }) - - try: - loadedfields = json.loads(fields) - for key, value in loadedfields.items(): - data["fields"].append({ - "key": key, - "value": value, + # Valid format: + # {"field1": "value1", "field2": "value2"} + # field1=value1&field2=value2 + # field1:value1\nfield2:value2 + + cursplit = None + if "\\n" in fields and not fields.startswith("{") and not fields.startswith("["): + cursplit = "\\n" + elif ("=" in fields or ":" in fields) and not fields.startswith("{") and not fields.startswith("["): + cursplit = "&" + + if cursplit: + newfields = [] + for line in fields.split(cursplit): + splitkey = None + if "=" in line: + splitkey = "=" + elif ":" in line: + splitkey = ":" + + if splitkey: + parts = line.split(splitkey, 1) + newfields.append({ + "key": parts[0].strip(), + "value": splitkey.join(parts[1:]).strip(), + }) + + data["fields"] = newfields + else: + if not fields.startswith("{") and not fields.startswith("["): + fields = json.dumps({ + "data": fields, }) - except Exception as e: - self.logger.info("[ERROR] Failed to load fields as JSON: %s" % e) - return json.dumps({ - "success": False, - "reason": "Ensure 'Fields' are valid JSON", - "details": "%s" % e, - }) - + try: + loadedfields = json.loads(fields) + for key, value in loadedfields.items(): + data["fields"].append({ + "key": key, + "value": value, + }) + + except Exception as e: + self.logger.info("[ERROR] Failed to load fields as JSON: %s" % e) + return json.dumps({ + "success": False, + "reason": "Ensure 'Fields' are valid JSON", + "details": "%s" % e, + }) #baseurl = "%s/api/v1/apps/categories/run" % self.base_url baseurl = "%s/api/v1/apps/categories/run" % self.url diff --git a/shuffle-tools/1.2.0/requirements.txt b/shuffle-tools/1.2.0/requirements.txt index 4fe45011..94c838b3 100644 --- a/shuffle-tools/1.2.0/requirements.txt +++ b/shuffle-tools/1.2.0/requirements.txt @@ -8,4 +8,4 @@ json2xml==5.0.5 ipaddress==1.0.23 google.auth==2.37.0 paramiko==3.5.0 -shuffle-sdk==0.0.31 +shuffle-sdk==0.0.33 diff --git a/shuffle-tools/1.2.0/src/app.py b/shuffle-tools/1.2.0/src/app.py index 85275cf2..0bea8a3f 100644 --- a/shuffle-tools/1.2.0/src/app.py +++ b/shuffle-tools/1.2.0/src/app.py @@ -235,6 +235,10 @@ def send_email_shuffle(self, apikey, recipients, subject, body, attachments=""): def repeat_back_to_me(self, call): return call + def repeat_back_to_me2(self, body): + print("call:", body) + return body + def dedup_and_merge(self, key, value, timeout, set_skipped=True): timeout = int(timeout) key = str(key) From e5b00f775e1fed039115bd8299ab049fc3c7b285 Mon Sep 17 00:00:00 2001 From: Frikky Date: Tue, 10 Feb 2026 12:36:10 +0100 Subject: [PATCH 256/259] Bumped shuffle-ai to be 1.1.0 to force re-downloads --- shuffle-ai/1.0.0/Dockerfile | 54 --- shuffle-ai/1.0.0/api.yaml | 167 -------- shuffle-ai/1.0.0/requirements.txt | 5 - shuffle-ai/1.0.0/src/app.py | 609 ------------------------------ shuffle-ai/1.0.0/upload.sh | 16 - 5 files changed, 851 deletions(-) delete mode 100644 shuffle-ai/1.0.0/Dockerfile delete mode 100644 shuffle-ai/1.0.0/api.yaml delete mode 100644 shuffle-ai/1.0.0/requirements.txt delete mode 100644 shuffle-ai/1.0.0/src/app.py delete mode 100755 shuffle-ai/1.0.0/upload.sh diff --git a/shuffle-ai/1.0.0/Dockerfile b/shuffle-ai/1.0.0/Dockerfile deleted file mode 100644 index 9b059f27..00000000 --- a/shuffle-ai/1.0.0/Dockerfile +++ /dev/null @@ -1,54 +0,0 @@ -FROM python:3.10-alpine - -# Install all alpine build tools needed for our pip installs -#RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev git poppler-utils - -# Install all of our pip packages in a single directory that we can copy to our base image later -RUN mkdir /install -WORKDIR /install - -# Switch back to our base image and copy in all of our built packages and source code -#COPY --from=builder /install /usr/local -COPY src /app -COPY requirements.txt /requirements.txt -RUN python3 -m pip install -r /requirements.txt - -# Install any binary dependencies needed in our final image -# RUN apk --no-cache add --update my_binary_dependency -RUN apk --no-cache add jq git curl - -ENV SHELL=/bin/bash - -### Install Tesseract -ENV CC /usr/bin/clang -ENV CXX /usr/bin/clang++ -ENV LANG=C.UTF-8 -ENV TESSDATA_PREFIX=/usr/local/share/tessdata - -# Dev tools -WORKDIR /tmp -RUN apk update -RUN apk upgrade -RUN apk add file openssl openssl-dev bash tini leptonica-dev openjpeg-dev tiff-dev libpng-dev zlib-dev libgcc mupdf-dev jbig2dec-dev -RUN apk add freetype-dev openblas-dev ffmpeg-dev linux-headers aspell-dev aspell-en # enchant-dev jasper-dev -RUN apk add --virtual .dev-deps git clang clang-dev g++ make automake autoconf libtool pkgconfig cmake ninja -RUN apk add --virtual .dev-testing-deps -X http://dl-3.alpinelinux.org/alpine/edge/testing autoconf-archive -RUN ln -s /usr/include/locale.h /usr/include/xlocale.h - -RUN apk add tesseract-ocr -RUN apk add poppler-utils - -# Install from main -RUN mkdir /usr/local/share/tessdata -RUN mkdir src -RUN cd src -RUN wget https://github.com/tesseract-ocr/tessdata_fast/raw/main/eng.traineddata -P /usr/local/share/tessdata -RUN git clone --depth 1 https://github.com/tesseract-ocr/tesseract.git - -#RUN curl -fsSL https://ollama.com/install.sh | sh -#RUN ollama pull llama3.2 -#RUN cd tesseract && ./autogen.sh && ./configure --build=x86_64-alpine-linux-musl --host=x86_64-alpine-linux-musl && make && make install && cd /tmp/src - -# Finally, lets run our app! -WORKDIR /app -CMD ["python", "app.py", "--log-level", "DEBUG"] diff --git a/shuffle-ai/1.0.0/api.yaml b/shuffle-ai/1.0.0/api.yaml deleted file mode 100644 index b9634f39..00000000 --- a/shuffle-ai/1.0.0/api.yaml +++ /dev/null @@ -1,167 +0,0 @@ ---- -app_version: 1.0.0 -name: Shuffle AI -description: An EXPERIMENTAL AI tool app for Shuffle -tags: - - AI - - Shuffle - - LLM -categories: - - AI - - LLM - - Shuffle -contact_info: - name: "@frikkylikeme" - url: https://shuffler.io - email: support@shuffler.io -actions: - #- name: run_llm - # description: "Runs a local LLM, with a GPU or CPU (slow). Default model is set up in Dockerfile" - # parameters: - # - name: input - # description: "The input question to the model" - # required: true - # multiline: true - # example: "" - # schema: - # type: string - # - name: system_message - # description: "The system message use, if any" - # required: false - # multiline: false - # example: "" - # schema: - # type: string - - - name: shuffle_cloud_inference - description: Input ANY kind of data in the format you want, and the format you want it in. Default is a business-y email. Uses ShuffleGPT, which is based on OpenAI and our own model. - parameters: - - name: apikey - description: Your https://shuffler.io apikey - required: true - multiline: false - example: "" - schema: - type: string - - name: text - description: The text you want to be converted (ANY format) - required: true - multiline: true - example: "Bad IPs are 1.2.3.4 and there's no good way to format this. JSON works too!" - schema: - type: string - - name: formatting - description: The format to use. - required: false - multiline: true - example: "Make it work as a ticket we can put in service now that is human readable for security analysts" - schema: - type: string - returns: - schema: - type: string - - name: generate_report - description: Input ANY kind of data in the format you want, and it will make an HTML report for you. This can be downloaded from the File location. - parameters: - - name: apikey - description: Your https://shuffler.io apikey - required: true - multiline: false - example: "" - schema: - type: string - - name: input_data - description: The text you want to be converted (ANY format) - required: true - multiline: true - example: "Bad IPs are 1.2.3.4 and there's no good way to format this. JSON works too!" - schema: - type: string - - name: report_title - description: The report title to be used in the report - required: true - multiline: true - example: "Statistics for October" - schema: - type: string - - name: report_name - description: The name of the HTML file - required: false - multiline: true - example: "statistics.html" - schema: - type: string - returns: - schema: - type: string - - name: extract_text_from_pdf - description: Returns text from a pdf - parameters: - - name: file_id - description: The file to find text in - required: true - multiline: false - example: "file_" - schema: - type: string - returns: - schema: - type: string - - name: extract_text_from_image - description: Returns text from an image - parameters: - - name: file_id - description: The file to find text in - required: true - multiline: false - example: "file_" - schema: - type: string - returns: - schema: - type: string - - name: run_schemaless - description: Runs an automatically translated action - parameters: - - name: category - description: The category the action is in - required: true - multiline: false - schema: - type: string - - name: action - description: The action label to run - required: true - multiline: false - schema: - type: string - - name: app_name - description: The app to run the action in - required: false - multiline: false - schema: - type: string - - name: fields - description: The additional fields to add - required: false - multiline: false - schema: - type: string - returns: - schema: - type: string - - name: transcribe_audio - description: Returns text from audio - parameters: - - name: file_id - description: The file containing the audio - required: true - multiline: false - example: "file_" - schema: - type: string - returns: - schema: - type: string - -large_image: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAK4AAACuCAYAAACvDDbuAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QA/wD/AP+gvaeTAAAAB3RJTUUH5AgXDjM6hEZGWwAAD+lJREFUeNrtXb/vJTcRH7/v3iVBCqRBiCAQAtHwq4AWRElHwX8AoqbmXwDRpiH/QyQkGoogUSAhKIKUAE1IdSRSREhQQk7c3XtD8X55vePxjNfe3bk3H+nu+96uPf54POtnj8fe8OQX30JwOIxhtzYBh6MGOsPF0z9p2iWwpd8LjX6W5vWUYaiqlBuvLT5b5TQDPlRwmMSAABBg+kCer+XuAeQf4tL9tAxJ/hIfZGSm8rhyEfjytfxr9FeSX+KjvVfipNVpWlaPNhsAEPCS7Ao8FYnRlbO4ksLnjiSQvIanv4FNjwJ5pXIlMq6MQpIqqPnQKQKbjuPDtZlG55o6UHXWtVncZZTbbNBVB1P5dJYguCbJJ1WjOG8PVOioSm5HPrVt1rwuyN+K+PSZnNV1M/MmEFubfFjjU9tmK9XBJ2cOk3DDdZiEG67DJOrGuA7HyvAe12ESAxa73KPrN1z8gUikCCdvcD5NXnpQpA8nNhh9m5Yn4ZMrV8dHV/8a/dRA0x419a3lI9GBtM2GcrGYFXRNUU5TyluTOpdXwqeUt6YOpby9DUTLZylOcRlzdBTf2yV3ZBFOmKSHQh5KpjSSSpqG4s6VkUubqw8W8knTSnWk0Y+2jF5tlmuDUloJn6T8gRVcEpJ+3srChHSNt8RJsq4p+S41LC13KTcu/RJt1pLPKY1Pzhwm4YbrMAk3XIdJTMe4aeCjJhBVk0YiQ1MWZHhLgmO5QNVWfKRlavlIIQnurQmcnaMjSbBxhtMwYUxODpLcl2tUhvPlNE6VkiuoFVLXKT6ZfBjxRIIzOSlgWpLSB8uZ0g3BjeVDlFGEos0mfKKL7CQrY2ES7pM2i/OX22w4/sWReEhEnUOTxx3a+FrawQGZh04/rWe6oJBKo5zT4zLjPHE9ZHym5YzToogzfQcmfLgOhuLF/Sjm2izVDyXnrKtcmmmdaKumf+RyCw5Xn7OmzQaJF0fiEZG6BjXpYUYaSVkaPrXeHe4eVaZEr3Prqrmmrbc2T8lrmOMjn5xJHeJLYkk+PfzNTxOflrwF0EeHbU0Zt2wsW+PTkncB7g5zmMSwzUfS4eDhPa7DJK5jXGorsnZxonbRIbeAoOUjkUvlp+qxFp9YNuWL0nBqsVCkqUsrHQnuX+Nx5/qcJDI0kWgtJh7ihYCN8aG+13DqOXlbWUfD+fN0AUEmp3RcUWlVEwCynb5ssYLnxHViJT6ULCykb8EnzUfpqBWfVAdcnt5tprGhIe10WnjHpB2FtMPWcpM66yXyOad4Lz4Srq34SHhwZfRos1w9Y/jkzGESvj3dYRLe4zpMwg3XYRJuuA6T4M/Hzfk/OGd9OP2HOE2f8wtBlCebJrkfp+Gc3AGmiSiuaVlpwkmajL4osPUm9FMqIzBOJolfjGuzEtdUwWl53Dm7Eh9pzIdps+FiYJyi1N+Rvs/6OLCQBul8Ip8R08ik3EwhLZz1Wv8XmU7ZZqX7OT2gUIB2oaRBm+2ovDm5nM+ulEeiD8yka8UnJ1PCP82r9YWW8iCU5XO8W/PhPmvllNKW7lEyszsgNKuzkspJFZFL15uPtIweq7A1xiKpz1J8tGXP+dE53/fJmcMk6hcgJO8XqokEKi5uYzTG29LqSev95JqyKsoOOxjNpKQBD7VFc5GBJRsi+NQHkkv6+7m/UxTufwLCCy+CbAruyOLDdwEf/uf6vbbNJukzlogZC6wMdhAcM7ohHPawe/GrcO+HPwe4u782G7sIAE9++0vYv/YKwO6usfCaka0etgwXAGB3D8JznwIYnlmbiW0M92FbQy0d+MmZ3Xo5JDDcvuXJ2ZYqtyUuTwuM6nSXctcufHCOZqkjPScXhbIcdeD0XUpfKyNNy8nlyhuozLkM8XxR6pjm7tc4Fdx620I7lWq10JCm0ZanWoBwm3FsBe1WznpadbTg4A9PI2xx7FUKHopQjg7TKqNnpbioIUcFUGUsy1CS8fFYBYdJuOE6TMIN12ESgyiKiwO1bQOJe1w+6p42Etmhwmi6kLZXfC2G9IUj2vulY2wIPrv4onRhIXcRqS0DiWxkhF0uIb37wG22LRCSuVCyekC2GSXj9CG3YyT+krWh+KPAhkTvgGDKqbqnWbBwY+2Pnm3Wy4aMRYc1MuPDvp0skwgAh8PaJGbh5k4kx0f/hce/ewnw/QenXQCTFJDfQy45PzFNn5NHsoPy/u6gzE+nObzz91P9Z+6kWAm2zg6bDMoq8OQxHN78Axze/htAaB1EbQhhdzyfgRqIGoCxoUIjhDuA3ZDpcR0W4C3nMInbNVw7v4oOAsehArVFPL0uOjMM+DlM+pk7t7/BDuwcJsM6gcM7WweOX05nFCHNi12ASRfLo3QaX9O0GWTylOTnZIMwf4YPPTlD4iMm7aZwAGOUf3Rf48wjHNzVOMkKFA8pp0RHZ1mjdihs5R61PWbsWlphgs/E5gptNvFfSLY8QPk7dVbh+UNg8qfnJsZ8Bo0hzF0Y2Nqvc0s+Vbs5YL5OLfPRcorT2hvjtuxyHWZhzHCX6AMcFtB2B0RvtKZqqe6OEYz1uA7HEbdruN7ZmsZtGq4brXnQhlsbLFkDrY9mC9giH41/dSlONfeEIBcgss7nXopInPdkYN95J3XD1bMgkJUNFOxsDNLgyiynhYyX5dnAhnLyhzmO4V7IO8+xyZEgx5UqvJ41rOUTdhBOr2w6KjZc+B1FBkLGVUoAABQEcmPu6rPPw73v/gh2n/wMANYEhAd4/NqvYf/Wn5pEyPW2IUrOzQWSHyHdkEJgN8D97/0Edp/7GgDu9fnDDvD9t+HRqy8BPvxQ9i6xEXUEuPcMDF//Puw+/aVqDewfvA77f/zx9M40e7jNeNw5CDu4++K34e4r36kWcXj3TYDfvwz8D79ml1clDPuxx9FhuUik0rblVihFWLX+7ZFEXE2ioLBNg9fUSRopVsOjJbioskZlDuyAvmflpOWsOUNu/cBQ8jW/1A0np11RG+GjwG36cQHqFWnBcG4Axgx37d/I1uXXcvCnx6BXoQXf3mOAzvVpooJzaOcWdKBH1fZ07dCsFZpNgmfZbaOJ2dxnpwkNFC3C9MBcGxo0OugxwV8LWKm5lg9sFQdszKGhLAla2dCuduuOZcypx+UXdk0OK5e/hXKNTc4cjiPGhtvTX1njI6Z2+vbuKtaKspLooXdkXs1u5yUR7/LdROMsraSSIfTa6pqWodE9Mvla6sCI8d7uUMEXIEzjdg3XYRr2osOePIbDR+9BGO7re78QAD/+AODwpK5sBDg6dGyGAtL1sYnLGDe3+2BNTNycYQf7B2/Aw5d/XB9HejjA4YN3jgHUNQ132MOTv/wG9v98A+CgFBCO/+FH/wJ89PBaSY1OULZzQyQL2skayVwg/7Dk3Ky2IlcEgEcfw/7dt+YJnRP1f9jDoz+/AvM0FU4c1u8mes59e+ZXDhXmPE+tForD+lH73Q6EluiozfaldnzWQUWQzdprPk87lg44nkTKN+DT/10S7lW4VYz8wWucOTAPtl5e4mgfjmu0/b3HdZiEG67DJNxwbxlGhwkAuZeXAJS3Qpfemq7dds1tS5dsbc6dAyQpS5uGe+lKrJLSGUqlCb2GcwUuCxBzt71T2/g7t9mQniofv0yjWOtMYdSLM6Sy0pd5iLdFSQtUyiJtRnjmGOdhqq5bo5WzUXAYzns2Lu2tjaqb0WaTHRBrR9cvEVG4VF3WkLsGnzXqohzjbk3dt4hG/jDDxy8BLL5y5miBZi1wa9vT14dJ0o2qft6/1GhQZ1SV9uJxd3cQ7j+XD7RJ40JK38/XAPKz4ly+OG+KwOTDwn0uDSKEZ58/vgH+hmHLcA97uPvCN+G5H/wMoCaQ/KkAAtzdg/DCZ9cmsipsGS4ce5u7z38DYHhmbTL2YfjBH28DOM80s+MoxllVvfkwKudSbiL0dB0NTya2iGpNYmIzl+/EdexjQ8PEGE4FhdPHMAlbLhcsdWaPnfDEAxQJnbx53TEPJ51j3N7CrEfbSNt+arzXt57X2RBx94LsUGHOGRQtF7Fa8HFQQOabJmc5XQ8b8iAbh0mYNFzvdefD+nRhyPowqWitc2VbRyutGCF18+ilU2mEXWX51zFuKbqlZ/RLy0gixzagiS6sgL2hghuwAywarsMBxgzXO9u2sBzZWHwHRLwrQ5rWYQBIfuwCKnZJEpvEYSg9dRoncnejtdxFbBRLqFQzr5fSudH3nDmOaH26yHIwNcZ1NIZNmwWArYU1Fg8HDLB/7wH879VfAey2Rd0a9g/+2ubUyZUOdAz//umXjT136GPd2cDNnM9bC4Pd1gbOx3WsDh/jOkzCDddhEpcjmKiFhvGLQwDitJNrYTz05H7MS+N56hiq0mbYCfeIj2STb2s+cSJEOrguJ4fScaneOW7kOWZJm4VCmaPFg8wKgcSGuLpzR49Rerm8vIRaaECgvyB1Tbl9qOZoMiykHeVhVoZKwW9N+CSJuPwsH4YY12aTa5TxYyZPpsxSDG/Rhgp1lyxUnK/7UMFhEm64DpNIlnzTAdXcsJml8rdO1yt/K+R45EJUluS9zHaWITuQJb9rsVT+HvuKe+RvhdIIcE3ey4Rj+VDBYRJuuA6TcMN1mMT15SWMZ5h10Oc86+dr50s14QWch7rEh5PHef+psgsyqB0iI2e+hE+pDlpvvkQ/uVUMDfdSnTq12TA58injFUdOMPB5AeiALtHcUrstXrqSINnaoVjxyE5ra1ZipHMsTV2kMiQ8NDw7tdmqQ4WtzNEd9uBjXIdJuOE6TMLoy0sct46KHndNS6d2pW5tp+rW+Jw5rVl2qpP5Oqrcnr52w9RMgbfA8db5tAsp8DGuwyTaGW6DB7ppn9CCzxKnvKz9Kz7j/prUi0cwqQLQDBtvrp5uvMc/Wf00oFAT5FjscbcwMloCt1LPWvTUT41sH+M6TMIN12ESw3UPd8gPtrh7JeTyXvZGn0KD0jSlMms5Sfhw92vkUvXT5tPWt3WbSfjMsSFl3ujlJdy+4xkjnFze+PWrNWXWclqaT6t82vq2bjMJnzk2pMzrQwWHSbjhOkzCDdchxpZchpezwySQvHhiyVMLevPRctXwqeWmfcv5GaVTGKRy557YIHnhpETeoCl05grhbPlL89HK1vCp5darvZbgo+XEwYcKDpNww3WYxC6/U5PY5oun66MzPHH8L05PpqHKghn+TpjyictkZQLPh4u6yeknvXeWU+JD6TDHJ/cbn93Bi8nnDKdJm8EG2+zIZwBudlbjUOYOpj1frClPwyf3OZuXuaEx3lgWZixKxIfZ911rvJO65PRFVmZjbYY+VHDYhBuuwyTccB0mcdkB0cr5z70pW/pm7Bo+LesgqUsrPjVye9WXkqld8FiizRCi6LBWjmTRPGGG/JZ5ejvoa1ai1qwvlWarbeZDBYdJuOE6TKKP4W7xJdFb4+R8ZvH5P852gxhpwOZ9AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIwLTA4LTIzVDE0OjUyOjAwKzAyOjAwetRgVgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyMC0wOC0yM1QxNDo1MTo1OCswMjowMJuxI+oAAAAASUVORK5CYII= diff --git a/shuffle-ai/1.0.0/requirements.txt b/shuffle-ai/1.0.0/requirements.txt deleted file mode 100644 index 62bee6e9..00000000 --- a/shuffle-ai/1.0.0/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -shuffle-sdk - -pytesseract -pdf2image -pypdf2 diff --git a/shuffle-ai/1.0.0/src/app.py b/shuffle-ai/1.0.0/src/app.py deleted file mode 100644 index e5f5e9bc..00000000 --- a/shuffle-ai/1.0.0/src/app.py +++ /dev/null @@ -1,609 +0,0 @@ -import os -import json -import tempfile -import requests - -try: - import pytesseract -except Exception as e: - print("Skipping pytesseract import: %s" % e) - -try: - import PyPDF2 -except Exception as e: - print("Skipping PyPDF2 import: %s" % e) - -try: - from pdf2image import convert_from_path -except Exception as e: - print("Skipping pdf2image import: %s" % e) - - -try: - import llama_cpp -except Exception as e: - print("Skipping llama_cpp import: %s" % e) - -print("LD Library: '%s'" % os.environ.get("LD_LIBRARY_PATH", "")) - -from shuffle_sdk import AppBase - -#model = "/models/Llama-3.2-3B.Q8_0.gguf" # Larger -#model = "/models/Llama-3.2-3B.Q2_K.gguf" # Smol - -#model = "/models/DeepSeek-R1-Distill-Llama-8B-Q2_K.gguf" # Smaller -#model = "/models/Meta-Llama-3-8B.Q6_K.gguf" -model = "/models/DeepSeek-R1-Distill-Llama.gguf" -if os.getenv("MODEL_PATH"): - model = os.getenv("MODEL_PATH") - -def load_llm_model(model): - print("Using model path '%s'" % model) - if not os.path.exists(model): - print("Could not find model at path %s" % model) - model_name = model.split("/")[-1] - # Check $HOME/downloads/{model} - - home_path = os.path.expanduser("~") - print(home_path) - - if os.path.exists(f"{home_path}/downloads/{model_name}"): - model = f"{home_path}/downloads/{model_name}" - else: - return { - "success": False, - "reason": "Model not found at path %s" % model, - "details": "Ensure the model path is correct" - } - - # Check for GPU layers - innerllm = None - gpu_layers = os.getenv("GPU_LAYERS") - if gpu_layers: - print("GPU Layers: %s" % gpu_layers) - - gpu_layers = int(gpu_layers) - if gpu_layers > 0: - innerllm = llama_cpp.Llama(model_path=model, n_gpu_layers=gpu_layers) - else: - innerllm = llama_cpp.Llama(model_path=model, n_gpu_layers=8) - else: - # Check if GPU available - print("No GPU layers set.") - #innerllm = llama_cpp.Llama(model_path=model) - - return { - "success": False, - "reason": "GPU layers not set", - "details": "Set GPU_LAYERS environment variable to the number of GPU layers to use (e.g. 8)." - } - - return innerllm - -try: - llm = load_llm_model(model) -except Exception as e: - print("[ERROR] Failed to load LLM model: %s" % e) - llm = { - "success": False, - "reason": "Failed to load LLM model %s" % model, - } - -class ShuffleAI(AppBase): - __version__ = "1.0.0" - app_name = "Shuffle AI" - - def __init__(self, redis, logger, console_logger=None): - super().__init__(redis, logger, console_logger) - - def run_llm(self, input, system_message=""): - global llm - global model - - self.logger.info("[DEBUG] LD LIbrary: '%s'. If this is empty, GPU's may not work." % os.environ.get("LD_LIBRARY_PATH", "")) - - if not system_message: - system_message = "Answer their question directly. Don't use HTML or Markdown", - - self.logger.info("[DEBUG] Running LLM with model '%s'. To overwrite path, use environment variable MODEL_PATH=" % model) - - # Check if llm is a dict or not and look for success and reason in it - if not llm: - return { - "success": False, - "reason": "LLM model not loaded", - "details": "Ensure the LLM model is loaded", - "gpu_layers": os.getenv("GPU_LAYERS"), - } - - if isinstance(llm, dict): - if "success" in llm and not llm["success"]: - # List files in /model folder - llm["folder"] = os.listdir("/models") - llm["gpu_layers"] = os.getenv("GPU_LAYERS") - return llm - - self.logger.info("[DEBUG] Running LLM with input '%s' and system message '%s'. GPU Layers: %s" % (input, system_message, os.getenv("GPU_LAYERS"))) - - # https://github.com/abetlen/llama-cpp-python - try: - print("LLM: ", llm) - - self.logger.info("[DEBUG] LLM: %s" % llm) - output = llm.create_chat_completion( - max_tokens=100, - messages = [ - { - "role": "system", - "content": system_message, - }, - { - "role": "user", - "content": input, - } - ] - ) - except Exception as e: - return { - "success": False, - "reason": f"Failed to run local LLM. Check logs in this execution for more info: {self.current_execution_id}", - "details": f"{e}" - } - - self.logger.info("[DEBUG] LLM output: %s" % output) - - new_message = "" - if "choices" in output and len(output["choices"]) > 0: - new_message = output["choices"][0]["message"]["content"] - - parsed_output = { - "success": True, - "model": output["model"], - "output": new_message, - } - - if "tokens" in output: - parsed_output["tokens"] = output["tokens"] - - if "usage" in output: - parsed_output["tokens"] = output["usage"] - - if not os.getenv("GPU_LAYERS"): - parsed_output["debug"] = "GPU_LAYERS not set. Running on CPU. Set GPU_LAYERS to the number of GPU layers to use (e.g. 8)." - - return parsed_output - - def security_assistant(self): - # Currently testing outside the Shuffle environment - # using assistants and local LLMs - - return "Not implemented" - - def shuffle_cloud_inference(self, apikey, text, formatting="auto"): - headers = { - "Authorization": "Bearer %s" % apikey, - } - - if not formatting: - formatting = "auto" - - output_formatting= "Format the following data to be a good email that can be sent to customers. Don't make it too business sounding." - if formatting != "auto": - output_formatting = formatting - - ret = requests.post( - "https://shuffler.io/api/v1/conversation", - json={ - "query": text, - "formatting": output_formatting, - "output_format": "formatting" - }, - headers=headers, - ) - - if ret.status_code != 200: - print(ret.text) - return { - "success": False, - "reason": "Status code for auto-formatter is not 200" - } - - return ret.text - - def autoformat_text(self, apikey, text, formatting="auto"): - headers = { - "Authorization": "Bearer %s" % apikey, - } - - if not formatting: - formatting = "auto" - - output_formatting= "Format the following data to be a good email that can be sent to customers. Don't make it too business sounding." - if formatting != "auto": - output_formatting = formatting - - ret = requests.post( - "https://shuffler.io/api/v1/conversation", - json={ - "query": text, - "formatting": output_formatting, - "output_format": "formatting" - }, - headers=headers, - ) - - if ret.status_code != 200: - print(ret.text) - return { - "success": False, - "reason": "Status code for auto-formatter is not 200" - } - - return ret.text - - def generate_report(self, apikey, input_data, report_title, report_name="generated_report.html"): - headers = { - "Authorization": "Bearer %s" % apikey, - } - - if not report_name: - report_name = "generated_report.html" - - if "." in report_name and not ".html" in report_name: - report_name = report_name.split(".")[0] - - if not "html" in report_name: - report_name = report_name + ".html" - - report_name = report_name.replace(" ", "_", -1) - output_formatting= "Format the following text into an HTML report with relevant graphs and tables. Title of the report should be {report_title}." - ret = requests.post( - "https://shuffler.io/api/v1/conversation", - json={ - "query": text, - "formatting": output_formatting, - "output_format": "formatting" - }, - headers=headers, - ) - - if ret.status_code != 200: - print(ret.text) - return { - "success": False, - "reason": "Status code for auto-formatter is not 200" - } - - # Make it into a shuffle file with self.set_files() - new_file = { - "name": report_name, - "data": ret.text, - } - - retdata = self.set_files([new_file]) - if retdata["success"]: - return retdata - - return { - "success": False, - "reason": "Failed to upload file" - } - - - def extract_text_from_pdf(self, file_id): - def extract_pdf_text(pdf_path): - with open(pdf_path, 'rb') as file: - pdf_reader = PyPDF2.PdfReader(file) - text = '' - for page in pdf_reader.pages: - text += page.extract_text() - - return text - - def extract_text_from_images(images): - text = '' - for image in images: - extracted_text = pytesseract.image_to_string(image, lang='eng') - text += extracted_text - return text - - def extract_text_from_pdf_with_images(pdf_path): - images = convert_from_path(pdf_path) - return extract_text_from_images(images) - - def export_text_to_json(image_text, extracted_text): - data = { - "success": True, - 'image_text': image_text, - 'extracted_text': extracted_text, - } - - #with open(output_path, 'w+') as file: - # json.dump(data, file, indent=4) - - return data - - pdf_data = self.get_file(file_id) - defaultdata = { - "success": False, - "file_id": file_id, - "filename": pdf_data["filename"], - "reason": "Something failed in reading and parsing the pdf. See error logs for more info", - } - - # Check type of pdf_data["data"] - if not isinstance(pdf_data["data"], bytes): - self.logger.info("Encoding data to bytes for the bytestream reader") - pdf_data["data"] = pdf_data["data"].encode() - - # Make a tempfile for the file data from self.get_file - # Make a tempfile with tempfile library - with tempfile.NamedTemporaryFile() as temp: - # Write the file data to the tempfile - # Get the path to the tempfile - temp.write(pdf_data["data"]) - pdf_path = temp.name - - # Extract text from the PDF - extracted_text_from_pdf = extract_pdf_text(pdf_path) - - # Extract text from the PDF using images - extracted_text_from_images = extract_text_from_pdf_with_images(pdf_path) - - # Combine the extracted text - - # Export combined text to JSON - #output_path = pdf_path.split(".")[0] + ".json" - exported_text = export_text_to_json(extracted_text_from_images, extracted_text_from_pdf) - exported_text["file_id"] = file_id - exported_text["filename"] = pdf_data["filename"] - return exported_text - - return defaultdata - - def extract_text_from_image(self, file_id): - # Check if it's a pdf - - pdf_data = self.get_file(file_id) - if "filename" not in pdf_data: - available_fields = [] - for key, value in pdf_data.items(): - available_fields.append(key) - - return { - "success": False, - "reason": "File not found", - "details": f"Available fields: {available_fields}", - } - - # If it is, use extract_text_from_pdf - # If it's not, use pytesseract - if pdf_data["filename"].endswith(".pdf"): - return self.extract_text_from_pdf(file_id) - - defaultdata = { - "success": False, - "file_id": file_id, - "filename": pdf_data["filename"], - "reason": "Something failed in reading and parsing the pdf. See error logs for more info", - } - - with tempfile.NamedTemporaryFile() as temp: - # Load temp as Image - # Write the file data to the tempfile - # Get the path to the tempfile - temp.write(pdf_data["data"]) - pdf_path = temp.name - - image = Image.open(temp.name) - image = image.resize((500,300)) - custom_config = r'-l eng --oem 3 --psm 6' - text = pytesseract.image_to_string(image,config=custom_config) - - data = { - "success": True, - 'extracted_text': text, - } - - return data - - return defaultdata - - def transcribe_audio(self, file_id): - return { - "success": False, - "reason": "Not implemented yet" - } - - def find_image_objects(self, file_id): - return { - "success": False, - "reason": "Not implemented yet" - } - - def gpt(self, input_text): - return { - "success": False, - "reason": "Not implemented yet" - } - - def run_agent(self, input_data, actions=None, apps=None): - prepared_format = { - "id": self.action["id"], - "params": { - "tool_name": self.action["app_name"], - "tool_id": self.action["app_id"], - "environment": self.action["environment"], - "input": { - "text": input_data, - } - }, - } - - if actions: - prepared_format["params"]["tool_name"] = actions - - if apps: - pass - - baseurl = f"{self.url}/api/v1/agent?execution_id={self.current_execution_id}&authorization={self.authorization}&action_id={self.action['id']}" - self.logger.info("[DEBUG] Running agent action with URL '%s'" % (baseurl)) - - headers = {} - request = requests.post( - baseurl, - json=prepared_format, - headers=headers, - ) - - # Random sleep timer to force delay - time.sleep(2) - # Gets into waiting state on backend - return json.dumps({ - "app_run": True, - "input_prompt": prepared_format, - "status": request.status_code, - "body": request.text, - }) - - def run_schemaless(self, category, action, app_name="", fields=""): - self.logger.info("[DEBUG] Running schemaless action with category '%s' and action label '%s'" % (category, action)) - - # Not necessary anymore - """ - action := shuffle.CategoryAction{ - Label: step.Name, - Category: step.Category, - AppName: step.AppName, - Fields: step.Fields, - - Environment: step.Environment, - - SkipWorkflow: true, - } - """ - - data = { - "label": action, - "category": category, - - "app_name": "", - "fields": [], - - "skip_workflow": True, - } - - if app_name: - data["app_name"] = app_name - - if fields: - if isinstance(fields, list): - data["fields"] = fields - - elif isinstance(fields, dict): - for key, value in fields.items(): - parsedvalue = str(value) - try: - if str(value).startswith("{") or str(value).startswith("["): - parsedvalue = json.dumps(value) - except: - pass - - data["fields"].append({ - "key": key, - "value": parsedvalue, - }) - - else: - fields = str(fields).strip() - # Valid format: - # {"field1": "value1", "field2": "value2"} - # field1=value1&field2=value2 - # field1:value1\nfield2:value2 - - cursplit = None - if "\\n" in fields and not fields.startswith("{") and not fields.startswith("["): - cursplit = "\\n" - elif ("=" in fields or ":" in fields) and not fields.startswith("{") and not fields.startswith("["): - cursplit = "&" - - if cursplit: - newfields = [] - for line in fields.split(cursplit): - splitkey = None - if "=" in line: - splitkey = "=" - elif ":" in line: - splitkey = ":" - - if splitkey: - parts = line.split(splitkey, 1) - newfields.append({ - "key": parts[0].strip(), - "value": splitkey.join(parts[1:]).strip(), - }) - - data["fields"] = newfields - else: - if not fields.startswith("{") and not fields.startswith("["): - fields = json.dumps({ - "data": fields, - }) - - try: - loadedfields = json.loads(fields) - for key, value in loadedfields.items(): - data["fields"].append({ - "key": key, - "value": value, - }) - - except Exception as e: - self.logger.info("[ERROR] Failed to load fields as JSON: %s" % e) - return json.dumps({ - "success": False, - "reason": "Ensure 'Fields' are valid JSON", - "details": "%s" % e, - }) - - #baseurl = "%s/api/v1/apps/categories/run" % self.base_url - baseurl = "%s/api/v1/apps/categories/run" % self.url - baseurl += "?execution_id=%s&authorization=%s" % (self.current_execution_id, self.authorization) - - self.logger.info("[DEBUG] Running schemaless action with URL '%s', category %s and action label %s" % (baseurl, category, action)) - - headers = {} - request = requests.post( - baseurl, - json=data, - headers=headers, - ) - - try: - if "parameters" in self.action: - response_headers = request.headers - for key, value in response_headers.items(): - if not str(key).lower().endswith("-url"): - continue - - self.action["parameters"].append({ - "name": key, - "value": value, - }) - - #self.logger.info("[DEBUG] Response header: %s: %s" % (key, value)) - except Exception as e: - self.logger.info("[ERROR] Failed to get response headers (category action url debug mapping): %s" % e) - - try: - data = request.json() - - #if "success" in data and "result" in data and "errors" in data: - # return data["result"] - - return data - except: - return request.text - -if __name__ == "__main__": - ShuffleAI.run() diff --git a/shuffle-ai/1.0.0/upload.sh b/shuffle-ai/1.0.0/upload.sh deleted file mode 100755 index b449aa4b..00000000 --- a/shuffle-ai/1.0.0/upload.sh +++ /dev/null @@ -1,16 +0,0 @@ -gcloud config set project shuffler - -gcloud beta run deploy shuffle-ai-1-0-0 \ - --project=shuffler \ - --region=europe-west4 \ - --source=./ \ - --max-instances=1 \ - --concurrency=64 \ - --gpu 1 --gpu-type=nvidia-l4 \ - --cpu 4 \ - --memory=16Gi \ - --no-cpu-throttling \ - --set-env-vars=MODEL_PATH=/models/DeepSeek-R1-Distill-Llama.gguf,GPU_LAYERS=64,SHUFFLE_APP_EXPOSED_PORT=8080,SHUFFLE_SWARM_CONFIG=run,SHUFFLE_LOGS_DISABLED=true,SHUFFLE_APP_SDK_TIMEOUT=300,LD_LIBRARY_PATH=/usr/local/lib:/usr/local/nvidia/lib64:$LD_LIBRARY_PATH \ - --source=./ \ - --service-account=shuffle-apps@shuffler.iam.gserviceaccount.com \ - --timeout=120s From e02aaadf983f7e610610b6f5e96865ce6a9cd1cb Mon Sep 17 00:00:00 2001 From: Frikky Date: Tue, 10 Feb 2026 13:04:23 +0100 Subject: [PATCH 257/259] Re-added shuffle-ai --- shuffle-ai/1.1.0/Dockerfile | 54 +++ shuffle-ai/1.1.0/Dockerfile_GPU | 108 ++++++ shuffle-ai/1.1.0/api.yaml | 167 ++++++++ shuffle-ai/1.1.0/requirements.txt | 5 + shuffle-ai/1.1.0/src/app.py | 609 ++++++++++++++++++++++++++++++ shuffle-ai/1.1.0/upload.sh | 16 + 6 files changed, 959 insertions(+) create mode 100644 shuffle-ai/1.1.0/Dockerfile create mode 100644 shuffle-ai/1.1.0/Dockerfile_GPU create mode 100644 shuffle-ai/1.1.0/api.yaml create mode 100644 shuffle-ai/1.1.0/requirements.txt create mode 100644 shuffle-ai/1.1.0/src/app.py create mode 100755 shuffle-ai/1.1.0/upload.sh diff --git a/shuffle-ai/1.1.0/Dockerfile b/shuffle-ai/1.1.0/Dockerfile new file mode 100644 index 00000000..9b059f27 --- /dev/null +++ b/shuffle-ai/1.1.0/Dockerfile @@ -0,0 +1,54 @@ +FROM python:3.10-alpine + +# Install all alpine build tools needed for our pip installs +#RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev git poppler-utils + +# Install all of our pip packages in a single directory that we can copy to our base image later +RUN mkdir /install +WORKDIR /install + +# Switch back to our base image and copy in all of our built packages and source code +#COPY --from=builder /install /usr/local +COPY src /app +COPY requirements.txt /requirements.txt +RUN python3 -m pip install -r /requirements.txt + +# Install any binary dependencies needed in our final image +# RUN apk --no-cache add --update my_binary_dependency +RUN apk --no-cache add jq git curl + +ENV SHELL=/bin/bash + +### Install Tesseract +ENV CC /usr/bin/clang +ENV CXX /usr/bin/clang++ +ENV LANG=C.UTF-8 +ENV TESSDATA_PREFIX=/usr/local/share/tessdata + +# Dev tools +WORKDIR /tmp +RUN apk update +RUN apk upgrade +RUN apk add file openssl openssl-dev bash tini leptonica-dev openjpeg-dev tiff-dev libpng-dev zlib-dev libgcc mupdf-dev jbig2dec-dev +RUN apk add freetype-dev openblas-dev ffmpeg-dev linux-headers aspell-dev aspell-en # enchant-dev jasper-dev +RUN apk add --virtual .dev-deps git clang clang-dev g++ make automake autoconf libtool pkgconfig cmake ninja +RUN apk add --virtual .dev-testing-deps -X http://dl-3.alpinelinux.org/alpine/edge/testing autoconf-archive +RUN ln -s /usr/include/locale.h /usr/include/xlocale.h + +RUN apk add tesseract-ocr +RUN apk add poppler-utils + +# Install from main +RUN mkdir /usr/local/share/tessdata +RUN mkdir src +RUN cd src +RUN wget https://github.com/tesseract-ocr/tessdata_fast/raw/main/eng.traineddata -P /usr/local/share/tessdata +RUN git clone --depth 1 https://github.com/tesseract-ocr/tesseract.git + +#RUN curl -fsSL https://ollama.com/install.sh | sh +#RUN ollama pull llama3.2 +#RUN cd tesseract && ./autogen.sh && ./configure --build=x86_64-alpine-linux-musl --host=x86_64-alpine-linux-musl && make && make install && cd /tmp/src + +# Finally, lets run our app! +WORKDIR /app +CMD ["python", "app.py", "--log-level", "DEBUG"] diff --git a/shuffle-ai/1.1.0/Dockerfile_GPU b/shuffle-ai/1.1.0/Dockerfile_GPU new file mode 100644 index 00000000..ed2798c2 --- /dev/null +++ b/shuffle-ai/1.1.0/Dockerfile_GPU @@ -0,0 +1,108 @@ +FROM python:3.10.18-slim + +# Switch back to our base image and copy in all of our built packages and source code +COPY requirements.txt /requirements.txt +# Check if requirements.txt contains llama-cpp-python or not +RUN grep -q "^llama-cpp-python" /requirements.txt \ + || (echo "❌ requirements.txt missing llama-cpp-python" && exit 1) + +### Install Tesseract +ENV SHELL=/bin/bash +ENV CC /usr/bin/clang +ENV CXX /usr/bin/clang++ +ENV LANG=C.UTF-8 +ENV TESSDATA_PREFIX=/usr/local/share/tessdata + +# Install all build tools needed for our pip installs +RUN apt update +RUN apt install -y clang g++ make automake autoconf libtool cmake + +## Install the same packages with apt as with apk, but ensure they exist in apt +RUN apt install -y jq git curl +RUN apt install -y file openssl bash tini libpng-dev aspell-en +RUN apt install -y git clang g++ make automake autoconf libtool cmake +RUN apt install -y autoconf-archive wget + +# Install cuda toolkit +#RUN wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/cuda-archive-keyring.gpg +#RUN dpkg -i cuda-archive-keyring.gpg +#RUN rm cuda-archive-keyring.gpg +#RUN apt update +#RUN apt install -y cuda +#RUN echo 'export PATH=/usr/local/cuda/bin:$PATH' >> ~/.bashrc +#RUN echo 'export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH' >> ~/.bashrc +#RUN source ~/.bashrc + +# Larger model +RUN mkdir -p /models + +# Fails. 6 bit, 8B model. +#RUN wget https://huggingface.co/RichardErkhov/meta-llama_-_Meta-Llama-3-8B-gguf/blob/main/Meta-Llama-3-8B.Q6_K.gguf?download=true -O /models/Meta-Llama-3-8B.Q6_K.gguf +#ENV MODEL_PATH="/models/Meta-Llama-3-8B.Q6_K.gguf" + +# Simple small Llama wrapper +RUN wget https://huggingface.co/unsloth/DeepSeek-R1-Distill-Llama-8B-GGUF/resolve/main/DeepSeek-R1-Distill-Llama-8B-Q2_K.gguf?download=true -O /models/DeepSeek-R1-Distill-Llama.gguf +# Larger one +#RUN wget https://huggingface.co/unsloth/DeepSeek-R1-Distill-Llama-8B-GGUF/resolve/main/DeepSeek-R1-Distill-Llama-8B-Q8_0.gguf?download=true -O /models/DeepSeek-R1-Distill-Llama.gguf +ENV MODEL_PATH="/models/DeepSeek-R1-Distill-Llama.gguf" + +# Failing? Bad magic bytes. +#RUN wget https://huggingface.co/QuantFactory/Llama-3.2-3B-GGUF/resolve/main/Llama-3.2-3B.Q2_K.gguf?download=true -O /models/Llama-3.2-3B.Q2_K.gguf + + +# Install all of our pip packages in a single directory that we can copy to our base image later +RUN mkdir /install +WORKDIR /install + +RUN python3 -m pip install -r /requirements.txt +RUN CMAKE_ARGS="-DLLAMA_CUBLAS=on" python3 -m pip install llama-cpp-python --upgrade --force-reinstall --no-cache-dir + + +# Install any binary dependencies needed in our final image + + +# Dev tools +WORKDIR /tmp +#RUN apk update +#RUN apk upgrade + + +RUN ln -s /usr/include/locale.h /usr/include/xlocale.h + +#RUN apk add tesseract-ocr +RUN apt install -y tesseract-ocr +#RUN apk add poppler-utils +RUN apt install -y poppler-utils +RUN apt clean && rm -rf /var/lib/apt/lists/* + +# Install from main +RUN mkdir /usr/local/share/tessdata +RUN wget https://github.com/tesseract-ocr/tessdata_fast/raw/main/eng.traineddata -P /usr/local/share/tessdata + +RUN mkdir src +RUN cd src + +RUN git clone --depth 1 https://github.com/tesseract-ocr/tesseract.git + +#RUN curl -fsSL https://ollama.com/install.sh | sh +# Install to /usr/local +#RUN wget https://ollama.com/install.sh -O /usr/local/bin/ollama-install +#RUN chmod +x /usr/local/bin/ollama-install +#RUN sh /usr/local/bin/ollama-install +# +#RUN ls -alh /usr/bin +#RUN ollama serve & sleep 2 && ollama pull nezahatkorkmaz/deepseek-v3 +#CMD ["sh", "-c", "ollama serve & sleep 2 && python app.py --log-level DEBUG"] + +#RUN wget https://huggingface.co/TheBloke/Mistral-7B-Instruct-v0.1-GGUF/resolve/main/mistral-7b-instruct-v0.1.Q4_K_M.gguf +RUN python3 -m pip install ctransformers --no-binary ctransformers + +# Finally, lets run our app! +ENV GIN_MODE=release +ENV SHUFFLE_APP_SDK_TIMEOUT=300 +#ENV LD_LIBRARY_PATH=/usr/local/lib/python3.10/site-packages/ctransformers/lib/basic/libctransformers.so +#RUN chmod 755 /usr/local/lib/python3.10/site-packages/ctransformers/lib/basic/libctransformers.so + +COPY src /app +WORKDIR /app +CMD ["python", "app.py", "--log-level", "DEBUG"] diff --git a/shuffle-ai/1.1.0/api.yaml b/shuffle-ai/1.1.0/api.yaml new file mode 100644 index 00000000..ab3f6243 --- /dev/null +++ b/shuffle-ai/1.1.0/api.yaml @@ -0,0 +1,167 @@ +--- +app_version: 1.1.0 +name: Shuffle AI +description: An EXPERIMENTAL AI tool app for Shuffle +tags: + - AI + - Shuffle + - LLM +categories: + - AI + - LLM + - Shuffle +contact_info: + name: "@frikkylikeme" + url: https://shuffler.io + email: support@shuffler.io +actions: + #- name: run_llm + # description: "Runs a local LLM, with a GPU or CPU (slow). Default model is set up in Dockerfile" + # parameters: + # - name: input + # description: "The input question to the model" + # required: true + # multiline: true + # example: "" + # schema: + # type: string + # - name: system_message + # description: "The system message use, if any" + # required: false + # multiline: false + # example: "" + # schema: + # type: string + + - name: shuffle_cloud_inference + description: Input ANY kind of data in the format you want, and the format you want it in. Default is a business-y email. Uses ShuffleGPT, which is based on OpenAI and our own model. + parameters: + - name: apikey + description: Your https://shuffler.io apikey + required: true + multiline: false + example: "" + schema: + type: string + - name: text + description: The text you want to be converted (ANY format) + required: true + multiline: true + example: "Bad IPs are 1.2.3.4 and there's no good way to format this. JSON works too!" + schema: + type: string + - name: formatting + description: The format to use. + required: false + multiline: true + example: "Make it work as a ticket we can put in service now that is human readable for security analysts" + schema: + type: string + returns: + schema: + type: string + - name: generate_report + description: Input ANY kind of data in the format you want, and it will make an HTML report for you. This can be downloaded from the File location. + parameters: + - name: apikey + description: Your https://shuffler.io apikey + required: true + multiline: false + example: "" + schema: + type: string + - name: input_data + description: The text you want to be converted (ANY format) + required: true + multiline: true + example: "Bad IPs are 1.2.3.4 and there's no good way to format this. JSON works too!" + schema: + type: string + - name: report_title + description: The report title to be used in the report + required: true + multiline: true + example: "Statistics for October" + schema: + type: string + - name: report_name + description: The name of the HTML file + required: false + multiline: true + example: "statistics.html" + schema: + type: string + returns: + schema: + type: string + - name: extract_text_from_pdf + description: Returns text from a pdf + parameters: + - name: file_id + description: The file to find text in + required: true + multiline: false + example: "file_" + schema: + type: string + returns: + schema: + type: string + - name: extract_text_from_image + description: Returns text from an image + parameters: + - name: file_id + description: The file to find text in + required: true + multiline: false + example: "file_" + schema: + type: string + returns: + schema: + type: string + - name: run_schemaless + description: Runs an automatically translated action + parameters: + - name: category + description: The category the action is in + required: true + multiline: false + schema: + type: string + - name: action + description: The action label to run + required: true + multiline: false + schema: + type: string + - name: app_name + description: The app to run the action in + required: false + multiline: false + schema: + type: string + - name: fields + description: The additional fields to add + required: false + multiline: false + schema: + type: string + returns: + schema: + type: string + - name: transcribe_audio + description: Returns text from audio + parameters: + - name: file_id + description: The file containing the audio + required: true + multiline: false + example: "file_" + schema: + type: string + returns: + schema: + type: string + +large_image: data:image/png;base64,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 diff --git a/shuffle-ai/1.1.0/requirements.txt b/shuffle-ai/1.1.0/requirements.txt new file mode 100644 index 00000000..62bee6e9 --- /dev/null +++ b/shuffle-ai/1.1.0/requirements.txt @@ -0,0 +1,5 @@ +shuffle-sdk + +pytesseract +pdf2image +pypdf2 diff --git a/shuffle-ai/1.1.0/src/app.py b/shuffle-ai/1.1.0/src/app.py new file mode 100644 index 00000000..e5f5e9bc --- /dev/null +++ b/shuffle-ai/1.1.0/src/app.py @@ -0,0 +1,609 @@ +import os +import json +import tempfile +import requests + +try: + import pytesseract +except Exception as e: + print("Skipping pytesseract import: %s" % e) + +try: + import PyPDF2 +except Exception as e: + print("Skipping PyPDF2 import: %s" % e) + +try: + from pdf2image import convert_from_path +except Exception as e: + print("Skipping pdf2image import: %s" % e) + + +try: + import llama_cpp +except Exception as e: + print("Skipping llama_cpp import: %s" % e) + +print("LD Library: '%s'" % os.environ.get("LD_LIBRARY_PATH", "")) + +from shuffle_sdk import AppBase + +#model = "/models/Llama-3.2-3B.Q8_0.gguf" # Larger +#model = "/models/Llama-3.2-3B.Q2_K.gguf" # Smol + +#model = "/models/DeepSeek-R1-Distill-Llama-8B-Q2_K.gguf" # Smaller +#model = "/models/Meta-Llama-3-8B.Q6_K.gguf" +model = "/models/DeepSeek-R1-Distill-Llama.gguf" +if os.getenv("MODEL_PATH"): + model = os.getenv("MODEL_PATH") + +def load_llm_model(model): + print("Using model path '%s'" % model) + if not os.path.exists(model): + print("Could not find model at path %s" % model) + model_name = model.split("/")[-1] + # Check $HOME/downloads/{model} + + home_path = os.path.expanduser("~") + print(home_path) + + if os.path.exists(f"{home_path}/downloads/{model_name}"): + model = f"{home_path}/downloads/{model_name}" + else: + return { + "success": False, + "reason": "Model not found at path %s" % model, + "details": "Ensure the model path is correct" + } + + # Check for GPU layers + innerllm = None + gpu_layers = os.getenv("GPU_LAYERS") + if gpu_layers: + print("GPU Layers: %s" % gpu_layers) + + gpu_layers = int(gpu_layers) + if gpu_layers > 0: + innerllm = llama_cpp.Llama(model_path=model, n_gpu_layers=gpu_layers) + else: + innerllm = llama_cpp.Llama(model_path=model, n_gpu_layers=8) + else: + # Check if GPU available + print("No GPU layers set.") + #innerllm = llama_cpp.Llama(model_path=model) + + return { + "success": False, + "reason": "GPU layers not set", + "details": "Set GPU_LAYERS environment variable to the number of GPU layers to use (e.g. 8)." + } + + return innerllm + +try: + llm = load_llm_model(model) +except Exception as e: + print("[ERROR] Failed to load LLM model: %s" % e) + llm = { + "success": False, + "reason": "Failed to load LLM model %s" % model, + } + +class ShuffleAI(AppBase): + __version__ = "1.0.0" + app_name = "Shuffle AI" + + def __init__(self, redis, logger, console_logger=None): + super().__init__(redis, logger, console_logger) + + def run_llm(self, input, system_message=""): + global llm + global model + + self.logger.info("[DEBUG] LD LIbrary: '%s'. If this is empty, GPU's may not work." % os.environ.get("LD_LIBRARY_PATH", "")) + + if not system_message: + system_message = "Answer their question directly. Don't use HTML or Markdown", + + self.logger.info("[DEBUG] Running LLM with model '%s'. To overwrite path, use environment variable MODEL_PATH=" % model) + + # Check if llm is a dict or not and look for success and reason in it + if not llm: + return { + "success": False, + "reason": "LLM model not loaded", + "details": "Ensure the LLM model is loaded", + "gpu_layers": os.getenv("GPU_LAYERS"), + } + + if isinstance(llm, dict): + if "success" in llm and not llm["success"]: + # List files in /model folder + llm["folder"] = os.listdir("/models") + llm["gpu_layers"] = os.getenv("GPU_LAYERS") + return llm + + self.logger.info("[DEBUG] Running LLM with input '%s' and system message '%s'. GPU Layers: %s" % (input, system_message, os.getenv("GPU_LAYERS"))) + + # https://github.com/abetlen/llama-cpp-python + try: + print("LLM: ", llm) + + self.logger.info("[DEBUG] LLM: %s" % llm) + output = llm.create_chat_completion( + max_tokens=100, + messages = [ + { + "role": "system", + "content": system_message, + }, + { + "role": "user", + "content": input, + } + ] + ) + except Exception as e: + return { + "success": False, + "reason": f"Failed to run local LLM. Check logs in this execution for more info: {self.current_execution_id}", + "details": f"{e}" + } + + self.logger.info("[DEBUG] LLM output: %s" % output) + + new_message = "" + if "choices" in output and len(output["choices"]) > 0: + new_message = output["choices"][0]["message"]["content"] + + parsed_output = { + "success": True, + "model": output["model"], + "output": new_message, + } + + if "tokens" in output: + parsed_output["tokens"] = output["tokens"] + + if "usage" in output: + parsed_output["tokens"] = output["usage"] + + if not os.getenv("GPU_LAYERS"): + parsed_output["debug"] = "GPU_LAYERS not set. Running on CPU. Set GPU_LAYERS to the number of GPU layers to use (e.g. 8)." + + return parsed_output + + def security_assistant(self): + # Currently testing outside the Shuffle environment + # using assistants and local LLMs + + return "Not implemented" + + def shuffle_cloud_inference(self, apikey, text, formatting="auto"): + headers = { + "Authorization": "Bearer %s" % apikey, + } + + if not formatting: + formatting = "auto" + + output_formatting= "Format the following data to be a good email that can be sent to customers. Don't make it too business sounding." + if formatting != "auto": + output_formatting = formatting + + ret = requests.post( + "https://shuffler.io/api/v1/conversation", + json={ + "query": text, + "formatting": output_formatting, + "output_format": "formatting" + }, + headers=headers, + ) + + if ret.status_code != 200: + print(ret.text) + return { + "success": False, + "reason": "Status code for auto-formatter is not 200" + } + + return ret.text + + def autoformat_text(self, apikey, text, formatting="auto"): + headers = { + "Authorization": "Bearer %s" % apikey, + } + + if not formatting: + formatting = "auto" + + output_formatting= "Format the following data to be a good email that can be sent to customers. Don't make it too business sounding." + if formatting != "auto": + output_formatting = formatting + + ret = requests.post( + "https://shuffler.io/api/v1/conversation", + json={ + "query": text, + "formatting": output_formatting, + "output_format": "formatting" + }, + headers=headers, + ) + + if ret.status_code != 200: + print(ret.text) + return { + "success": False, + "reason": "Status code for auto-formatter is not 200" + } + + return ret.text + + def generate_report(self, apikey, input_data, report_title, report_name="generated_report.html"): + headers = { + "Authorization": "Bearer %s" % apikey, + } + + if not report_name: + report_name = "generated_report.html" + + if "." in report_name and not ".html" in report_name: + report_name = report_name.split(".")[0] + + if not "html" in report_name: + report_name = report_name + ".html" + + report_name = report_name.replace(" ", "_", -1) + output_formatting= "Format the following text into an HTML report with relevant graphs and tables. Title of the report should be {report_title}." + ret = requests.post( + "https://shuffler.io/api/v1/conversation", + json={ + "query": text, + "formatting": output_formatting, + "output_format": "formatting" + }, + headers=headers, + ) + + if ret.status_code != 200: + print(ret.text) + return { + "success": False, + "reason": "Status code for auto-formatter is not 200" + } + + # Make it into a shuffle file with self.set_files() + new_file = { + "name": report_name, + "data": ret.text, + } + + retdata = self.set_files([new_file]) + if retdata["success"]: + return retdata + + return { + "success": False, + "reason": "Failed to upload file" + } + + + def extract_text_from_pdf(self, file_id): + def extract_pdf_text(pdf_path): + with open(pdf_path, 'rb') as file: + pdf_reader = PyPDF2.PdfReader(file) + text = '' + for page in pdf_reader.pages: + text += page.extract_text() + + return text + + def extract_text_from_images(images): + text = '' + for image in images: + extracted_text = pytesseract.image_to_string(image, lang='eng') + text += extracted_text + return text + + def extract_text_from_pdf_with_images(pdf_path): + images = convert_from_path(pdf_path) + return extract_text_from_images(images) + + def export_text_to_json(image_text, extracted_text): + data = { + "success": True, + 'image_text': image_text, + 'extracted_text': extracted_text, + } + + #with open(output_path, 'w+') as file: + # json.dump(data, file, indent=4) + + return data + + pdf_data = self.get_file(file_id) + defaultdata = { + "success": False, + "file_id": file_id, + "filename": pdf_data["filename"], + "reason": "Something failed in reading and parsing the pdf. See error logs for more info", + } + + # Check type of pdf_data["data"] + if not isinstance(pdf_data["data"], bytes): + self.logger.info("Encoding data to bytes for the bytestream reader") + pdf_data["data"] = pdf_data["data"].encode() + + # Make a tempfile for the file data from self.get_file + # Make a tempfile with tempfile library + with tempfile.NamedTemporaryFile() as temp: + # Write the file data to the tempfile + # Get the path to the tempfile + temp.write(pdf_data["data"]) + pdf_path = temp.name + + # Extract text from the PDF + extracted_text_from_pdf = extract_pdf_text(pdf_path) + + # Extract text from the PDF using images + extracted_text_from_images = extract_text_from_pdf_with_images(pdf_path) + + # Combine the extracted text + + # Export combined text to JSON + #output_path = pdf_path.split(".")[0] + ".json" + exported_text = export_text_to_json(extracted_text_from_images, extracted_text_from_pdf) + exported_text["file_id"] = file_id + exported_text["filename"] = pdf_data["filename"] + return exported_text + + return defaultdata + + def extract_text_from_image(self, file_id): + # Check if it's a pdf + + pdf_data = self.get_file(file_id) + if "filename" not in pdf_data: + available_fields = [] + for key, value in pdf_data.items(): + available_fields.append(key) + + return { + "success": False, + "reason": "File not found", + "details": f"Available fields: {available_fields}", + } + + # If it is, use extract_text_from_pdf + # If it's not, use pytesseract + if pdf_data["filename"].endswith(".pdf"): + return self.extract_text_from_pdf(file_id) + + defaultdata = { + "success": False, + "file_id": file_id, + "filename": pdf_data["filename"], + "reason": "Something failed in reading and parsing the pdf. See error logs for more info", + } + + with tempfile.NamedTemporaryFile() as temp: + # Load temp as Image + # Write the file data to the tempfile + # Get the path to the tempfile + temp.write(pdf_data["data"]) + pdf_path = temp.name + + image = Image.open(temp.name) + image = image.resize((500,300)) + custom_config = r'-l eng --oem 3 --psm 6' + text = pytesseract.image_to_string(image,config=custom_config) + + data = { + "success": True, + 'extracted_text': text, + } + + return data + + return defaultdata + + def transcribe_audio(self, file_id): + return { + "success": False, + "reason": "Not implemented yet" + } + + def find_image_objects(self, file_id): + return { + "success": False, + "reason": "Not implemented yet" + } + + def gpt(self, input_text): + return { + "success": False, + "reason": "Not implemented yet" + } + + def run_agent(self, input_data, actions=None, apps=None): + prepared_format = { + "id": self.action["id"], + "params": { + "tool_name": self.action["app_name"], + "tool_id": self.action["app_id"], + "environment": self.action["environment"], + "input": { + "text": input_data, + } + }, + } + + if actions: + prepared_format["params"]["tool_name"] = actions + + if apps: + pass + + baseurl = f"{self.url}/api/v1/agent?execution_id={self.current_execution_id}&authorization={self.authorization}&action_id={self.action['id']}" + self.logger.info("[DEBUG] Running agent action with URL '%s'" % (baseurl)) + + headers = {} + request = requests.post( + baseurl, + json=prepared_format, + headers=headers, + ) + + # Random sleep timer to force delay + time.sleep(2) + # Gets into waiting state on backend + return json.dumps({ + "app_run": True, + "input_prompt": prepared_format, + "status": request.status_code, + "body": request.text, + }) + + def run_schemaless(self, category, action, app_name="", fields=""): + self.logger.info("[DEBUG] Running schemaless action with category '%s' and action label '%s'" % (category, action)) + + # Not necessary anymore + """ + action := shuffle.CategoryAction{ + Label: step.Name, + Category: step.Category, + AppName: step.AppName, + Fields: step.Fields, + + Environment: step.Environment, + + SkipWorkflow: true, + } + """ + + data = { + "label": action, + "category": category, + + "app_name": "", + "fields": [], + + "skip_workflow": True, + } + + if app_name: + data["app_name"] = app_name + + if fields: + if isinstance(fields, list): + data["fields"] = fields + + elif isinstance(fields, dict): + for key, value in fields.items(): + parsedvalue = str(value) + try: + if str(value).startswith("{") or str(value).startswith("["): + parsedvalue = json.dumps(value) + except: + pass + + data["fields"].append({ + "key": key, + "value": parsedvalue, + }) + + else: + fields = str(fields).strip() + # Valid format: + # {"field1": "value1", "field2": "value2"} + # field1=value1&field2=value2 + # field1:value1\nfield2:value2 + + cursplit = None + if "\\n" in fields and not fields.startswith("{") and not fields.startswith("["): + cursplit = "\\n" + elif ("=" in fields or ":" in fields) and not fields.startswith("{") and not fields.startswith("["): + cursplit = "&" + + if cursplit: + newfields = [] + for line in fields.split(cursplit): + splitkey = None + if "=" in line: + splitkey = "=" + elif ":" in line: + splitkey = ":" + + if splitkey: + parts = line.split(splitkey, 1) + newfields.append({ + "key": parts[0].strip(), + "value": splitkey.join(parts[1:]).strip(), + }) + + data["fields"] = newfields + else: + if not fields.startswith("{") and not fields.startswith("["): + fields = json.dumps({ + "data": fields, + }) + + try: + loadedfields = json.loads(fields) + for key, value in loadedfields.items(): + data["fields"].append({ + "key": key, + "value": value, + }) + + except Exception as e: + self.logger.info("[ERROR] Failed to load fields as JSON: %s" % e) + return json.dumps({ + "success": False, + "reason": "Ensure 'Fields' are valid JSON", + "details": "%s" % e, + }) + + #baseurl = "%s/api/v1/apps/categories/run" % self.base_url + baseurl = "%s/api/v1/apps/categories/run" % self.url + baseurl += "?execution_id=%s&authorization=%s" % (self.current_execution_id, self.authorization) + + self.logger.info("[DEBUG] Running schemaless action with URL '%s', category %s and action label %s" % (baseurl, category, action)) + + headers = {} + request = requests.post( + baseurl, + json=data, + headers=headers, + ) + + try: + if "parameters" in self.action: + response_headers = request.headers + for key, value in response_headers.items(): + if not str(key).lower().endswith("-url"): + continue + + self.action["parameters"].append({ + "name": key, + "value": value, + }) + + #self.logger.info("[DEBUG] Response header: %s: %s" % (key, value)) + except Exception as e: + self.logger.info("[ERROR] Failed to get response headers (category action url debug mapping): %s" % e) + + try: + data = request.json() + + #if "success" in data and "result" in data and "errors" in data: + # return data["result"] + + return data + except: + return request.text + +if __name__ == "__main__": + ShuffleAI.run() diff --git a/shuffle-ai/1.1.0/upload.sh b/shuffle-ai/1.1.0/upload.sh new file mode 100755 index 00000000..b449aa4b --- /dev/null +++ b/shuffle-ai/1.1.0/upload.sh @@ -0,0 +1,16 @@ +gcloud config set project shuffler + +gcloud beta run deploy shuffle-ai-1-0-0 \ + --project=shuffler \ + --region=europe-west4 \ + --source=./ \ + --max-instances=1 \ + --concurrency=64 \ + --gpu 1 --gpu-type=nvidia-l4 \ + --cpu 4 \ + --memory=16Gi \ + --no-cpu-throttling \ + --set-env-vars=MODEL_PATH=/models/DeepSeek-R1-Distill-Llama.gguf,GPU_LAYERS=64,SHUFFLE_APP_EXPOSED_PORT=8080,SHUFFLE_SWARM_CONFIG=run,SHUFFLE_LOGS_DISABLED=true,SHUFFLE_APP_SDK_TIMEOUT=300,LD_LIBRARY_PATH=/usr/local/lib:/usr/local/nvidia/lib64:$LD_LIBRARY_PATH \ + --source=./ \ + --service-account=shuffle-apps@shuffler.iam.gserviceaccount.com \ + --timeout=120s From 924b8ff4a15fb9d5a7098f71d83a8949841fd1a0 Mon Sep 17 00:00:00 2001 From: Frikky Date: Tue, 10 Feb 2026 13:10:28 +0100 Subject: [PATCH 258/259] Fixed requirements --- shuffle-ai/1.1.0/requirements.txt | 2 +- shuffle-tools/1.2.0/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/shuffle-ai/1.1.0/requirements.txt b/shuffle-ai/1.1.0/requirements.txt index 62bee6e9..bb5d1927 100644 --- a/shuffle-ai/1.1.0/requirements.txt +++ b/shuffle-ai/1.1.0/requirements.txt @@ -1,4 +1,4 @@ -shuffle-sdk +shuffle-sdk==0.0.35 pytesseract pdf2image diff --git a/shuffle-tools/1.2.0/requirements.txt b/shuffle-tools/1.2.0/requirements.txt index 94c838b3..83301149 100644 --- a/shuffle-tools/1.2.0/requirements.txt +++ b/shuffle-tools/1.2.0/requirements.txt @@ -8,4 +8,4 @@ json2xml==5.0.5 ipaddress==1.0.23 google.auth==2.37.0 paramiko==3.5.0 -shuffle-sdk==0.0.33 +shuffle-sdk==0.0.35 From ad44e7b3fbd17778063d1c4041029e60ede87878 Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 11 Feb 2026 15:18:15 +0100 Subject: [PATCH 259/259] Added time library to shuffle-ai --- shuffle-ai/1.1.0/src/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/shuffle-ai/1.1.0/src/app.py b/shuffle-ai/1.1.0/src/app.py index e5f5e9bc..8785b341 100644 --- a/shuffle-ai/1.1.0/src/app.py +++ b/shuffle-ai/1.1.0/src/app.py @@ -2,6 +2,7 @@ import json import tempfile import requests +import time try: import pytesseract