diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 0a0ad1e..c543030 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,11 +1,11 @@
repos:
- repo: https://github.com/phantomcyber/dev-cicd-tools
- rev: v1.16
+ rev: v1.24
hooks:
- id: org-hook
- id: package-app-dependencies
- repo: https://github.com/Yelp/detect-secrets
- rev: v1.4.0
+ rev: v1.5.0
hooks:
- id: detect-secrets
args: ['--no-verify', '--exclude-files', '^jira.json$']
diff --git a/README.md b/README.md
index 6450950..52b4c13 100644
--- a/README.md
+++ b/README.md
@@ -2,11 +2,11 @@
# Jira
Publisher: Splunk
-Connector Version: 3.7.1
+Connector Version: 3.7.2
Product Vendor: Atlassian
Product Name: Jira
Product Version Supported (regex): ".\*"
-Minimum Product Version: 6.1.1
+Minimum Product Version: 6.3.0
This app integrates with JIRA to perform several ticket management actions
@@ -342,8 +342,8 @@ ports used by the Splunk SOAR Connector.
| https | tcp | 443 |
-### Configuration Variables
-The below configuration variables are required for this Connector to operate. These variables are specified when configuring a Jira asset in SOAR.
+### Configuration variables
+This table lists the configuration variables required to operate Jira. These variables are specified when configuring a Jira asset in Splunk SOAR.
VARIABLE | REQUIRED | TYPE | DESCRIPTION
-------- | -------- | ---- | -----------
diff --git a/jira.json b/jira.json
index 408a051..503d3ba 100644
--- a/jira.json
+++ b/jira.json
@@ -10,13 +10,13 @@
],
"type": "ticketing",
"main_module": "jira_connector.py",
- "app_version": "3.7.1",
+ "app_version": "3.7.2",
"utctime_updated": "2024-02-19T12:22:11.000000Z",
"package_name": "phantom_jira",
"product_vendor": "Atlassian",
"product_name": "Jira",
"product_version_regex": ".*",
- "min_phantom_version": "6.1.1",
+ "min_phantom_version": "6.3.0",
"latest_tested_versions": [
"On prem v8.21.0",
"Cloud Copyright (c) 2002 - 2023 Atlassian Corporation Pty Ltd."
@@ -10669,18 +10669,6 @@
"module": "PyJWT",
"input_file": "wheels/py3/PyJWT-2.6.0-py3-none-any.whl"
},
- {
- "module": "beautifulsoup4",
- "input_file": "wheels/py3/beautifulsoup4-4.9.1-py3-none-any.whl"
- },
- {
- "module": "cffi",
- "input_file": "wheels/py39/cffi-1.16.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl"
- },
- {
- "module": "cryptography",
- "input_file": "wheels/py36/cryptography-40.0.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl"
- },
{
"module": "defusedxml",
"input_file": "wheels/shared/defusedxml-0.7.1-py2.py3-none-any.whl"
@@ -10695,20 +10683,12 @@
},
{
"module": "packaging",
- "input_file": "wheels/py3/packaging-23.2-py3-none-any.whl"
+ "input_file": "wheels/py3/packaging-24.2-py3-none-any.whl"
},
{
"module": "pbr",
"input_file": "wheels/shared/pbr-5.4.4-py2.py3-none-any.whl"
},
- {
- "module": "pycparser",
- "input_file": "wheels/shared/pycparser-2.21-py2.py3-none-any.whl"
- },
- {
- "module": "python_dateutil",
- "input_file": "wheels/shared/python_dateutil-2.8.1-py2.py3-none-any.whl"
- },
{
"module": "pytz",
"input_file": "wheels/shared/pytz-2021.1-py2.py3-none-any.whl"
@@ -10722,13 +10702,9 @@
"input_file": "wheels/shared/requests_toolbelt-0.10.1-py2.py3-none-any.whl"
},
{
- "module": "six",
- "input_file": "wheels/shared/six-1.16.0-py2.py3-none-any.whl"
- },
- {
- "module": "soupsieve",
- "input_file": "wheels/py3/soupsieve-2.5-py3-none-any.whl"
+ "module": "typing_extensions",
+ "input_file": "wheels/py3/typing_extensions-4.12.2-py3-none-any.whl"
}
]
}
-}
\ No newline at end of file
+}
diff --git a/jira_connector.py b/jira_connector.py
index 4b13194..5bf8e74 100644
--- a/jira_connector.py
+++ b/jira_connector.py
@@ -34,7 +34,6 @@
from phantom_common import paths
from jira.client import JIRA
-# THIS Connector imports
from jira_consts import *
@@ -78,7 +77,7 @@ def initialize(self):
# Base URL
self._base_url = config[JIRA_JSON_DEVICE_URL]
- self._host = self._base_url[self._base_url.find('//') + 2:]
+ self._host = self._base_url[self._base_url.find("//") + 2 :]
self._timezone = config.get(JIRA_JSON_TIMEZONE, JIRA_JSON_DEFAULT_TIMEZONE)
self._verify_cert = config.get(phantom.APP_JSON_VERIFY, False)
@@ -99,7 +98,7 @@ def initialize(self):
return phantom.APP_SUCCESS
def _process_empty_response(self, response, action_result):
- """ This function is used to process empty response.
+ """This function is used to process empty response.
:param response: response data
:param action_result: object of Action Result
:return: status phantom.APP_ERROR/phantom.APP_SUCCESS(along with appropriate message)
@@ -108,11 +107,15 @@ def _process_empty_response(self, response, action_result):
if response.status_code == 200 or response.status_code == 204:
return phantom.APP_SUCCESS, {}
- return action_result.set_status(phantom.APP_ERROR,
- "Status code: {}. Empty response and no information in the header".format(response.status_code)), None
+ return (
+ action_result.set_status(
+ phantom.APP_ERROR, "Status code: {}. Empty response and no information in the header".format(response.status_code)
+ ),
+ None,
+ )
def _process_html_response(self, response, action_result):
- """ This function is used to process html response.
+ """This function is used to process html response.
:param response: response data
:param action_result: object of Action Result
:return: status phantom.APP_ERROR/phantom.APP_SUCCESS(along with appropriate message)
@@ -127,20 +130,20 @@ def _process_html_response(self, response, action_result):
for element in soup(["script", "style", "footer", "nav"]):
element.extract()
error_text = soup.text
- split_lines = error_text.split('\n')
+ split_lines = error_text.split("\n")
split_lines = [x.strip() for x in split_lines if x.strip()]
- error_text = '\n'.join(split_lines)
+ error_text = "\n".join(split_lines)
except Exception:
error_text = "Cannot parse error details"
message = "Status Code: {0}. Data from server:\n{1}\n".format(status_code, error_text)
- message = message.replace('{', '{{').replace('}', '}}')
+ message = message.replace("{", "{{").replace("}", "}}")
return action_result.set_status(phantom.APP_ERROR, message), None
def _process_json_response(self, r, action_result):
- """ This function is used to process json response.
+ """This function is used to process json response.
:param r: response data
:param action_result: object of Action Result
:return: status phantom.APP_ERROR/phantom.APP_SUCCESS(along with appropriate message)
@@ -151,8 +154,7 @@ def _process_json_response(self, r, action_result):
resp_json = r.json()
except Exception as e:
error_text = self._get_error_message_from_exception(e)
- return action_result.set_status(phantom.APP_ERROR,
- "Unable to parse JSON response. {}".format(error_text)), None
+ return action_result.set_status(phantom.APP_ERROR, "Unable to parse JSON response. {}".format(error_text)), None
# Please specify the status codes here
if 200 <= r.status_code < 399:
@@ -160,34 +162,34 @@ def _process_json_response(self, r, action_result):
message = None
# Error handling for different type of error responses from server
- if resp_json.get('errorMessages'):
- resp_message = ", ".join(resp_json.get('errorMessages', "Error message not found"))
+ if resp_json.get("errorMessages"):
+ resp_message = ", ".join(resp_json.get("errorMessages", "Error message not found"))
message = "Error from server. Status code: {}. Data from server: {}".format(r.status_code, resp_message)
# You should process the error returned in the json if none of the above handling happens for error scenario
if not message:
- resp_text = r.text.replace('{', '{{').replace('}', '}}') if r.text else "Response error text not found"
+ resp_text = r.text.replace("{", "{{").replace("}", "}}") if r.text else "Response error text not found"
message = "Error from server. Status Code: {0} Data from server: {1}".format(r.status_code, resp_text)
return action_result.set_status(phantom.APP_ERROR, message), None
def _process_response(self, r, action_result):
- """ This function is used to process API response.
+ """This function is used to process API response.
:param r: response data
:param action_result: object of Action Result
:return: status phantom.APP_ERROR/phantom.APP_SUCCESS(along with appropriate message)
"""
# Store the r_text in debug data, it will get dumped in the logs if the action fails
- if hasattr(action_result, 'add_debug_data'):
- action_result.add_debug_data({'r_status_code': r.status_code})
- action_result.add_debug_data({'r_text': r.text})
- action_result.add_debug_data({'r_headers': r.headers})
+ if hasattr(action_result, "add_debug_data"):
+ action_result.add_debug_data({"r_status_code": r.status_code})
+ action_result.add_debug_data({"r_text": r.text})
+ action_result.add_debug_data({"r_headers": r.headers})
# Process each 'Content-Type' of response separately
# Process a json response
- if 'json' in r.headers.get('Content-Type', ''):
+ if "json" in r.headers.get("Content-Type", ""):
if not r.text:
return self._process_empty_response(r, action_result)
return self._process_json_response(r, action_result)
@@ -196,7 +198,7 @@ def _process_response(self, r, action_result):
# There is a high chance of a PROXY in between phantom and the rest of
# world, in case of errors, PROXY's return HTML, this function parses
# the error and adds it to the action_result.
- if 'html' in r.headers.get('Content-Type', ''):
+ if "html" in r.headers.get("Content-Type", ""):
return self._process_html_response(r, action_result)
# it's not content-type that is to be parsed, handle an empty response
@@ -205,12 +207,13 @@ def _process_response(self, r, action_result):
# everything else is actually an error at this point
message = "Can't process response from server. Status Code: {0} Data from server: {1}".format(
- r.status_code, r.text.replace('{', '{{').replace('}', '}}') if r.text else "Response error text not found")
+ r.status_code, r.text.replace("{", "{{").replace("}", "}}") if r.text else "Response error text not found"
+ )
return action_result.set_status(phantom.APP_ERROR, message), None
def _make_rest_call(self, endpoint, action_result, method="get", params=None, data=None):
- """ Function that makes the REST call to the app.
+ """Function that makes the REST call to the app.
:param endpoint: REST endpoint that needs to appended to the service address
:param action_result: object of ActionResult class
:param method: GET/POST/PUT/DELETE/PATCH (Default will be GET)
@@ -229,7 +232,7 @@ def _make_rest_call(self, endpoint, action_result, method="get", params=None, da
# Create headers information
headers = dict()
- headers.update({'Content-Type': 'application/json'})
+ headers.update({"Content-Type": "application/json"})
# Create a URL to connect to Jira server
url = "{}/rest/api/2/{}".format(self._base_url, endpoint)
@@ -238,16 +241,16 @@ def _make_rest_call(self, endpoint, action_result, method="get", params=None, da
try:
if self._username:
- r = request_func(url, auth=(self._username, self._password), params=params, headers=headers,
- data=data, timeout=JIRA_DEFAULT_TIMEOUT)
+ r = request_func(
+ url, auth=(self._username, self._password), params=params, headers=headers, data=data, timeout=JIRA_DEFAULT_TIMEOUT
+ )
else:
self.debug_print("Updating headers with Bearer token authorization")
- headers.update({'Authorization': 'Bearer {}'.format(self._password)})
+ headers.update({"Authorization": "Bearer {}".format(self._password)})
r = request_func(url, params=params, headers=headers, data=data, timeout=JIRA_DEFAULT_TIMEOUT)
except Exception as e:
error_text = self._get_error_message_from_exception(e)
- return action_result.set_status(phantom.APP_ERROR,
- "Error connecting to server. {}".format(error_text)), resp_json
+ return action_result.set_status(phantom.APP_ERROR, "Error connecting to server. {}".format(error_text)), resp_json
return self._process_response(r, action_result)
@@ -299,7 +302,7 @@ def _load_state(self):
state = {}
try:
- with open(state_file_path, 'r') as f:
+ with open(state_file_path, "r") as f:
in_json = f.read()
state = json.loads(in_json)
except Exception as e:
@@ -323,7 +326,7 @@ def _save_state(self, state):
return phantom.APP_SUCCESS
try:
- with open(state_file_path, 'w+') as f:
+ with open(state_file_path, "w+") as f:
f.write(json.dumps(state))
except Exception:
pass
@@ -337,15 +340,14 @@ def _set_jira_error(self, result_object, message, e):
if hasattr(e, "text") and e.text:
error_text: str = self._extract_err_msg_from_jira_exc_text(e)
else:
- self.debug_print(
- "Jira error details missing in exception. Details will be "
- "fetched from HTTP Response."
- )
+ self.debug_print("Jira error details missing in exception. Details will be " "fetched from HTTP Response.")
error_text = self._extract_err_msg_from_jira_exc_response(e)
if "Epic Name is required" in error_text:
- error_text = "{}. {}".format(error_text,
- "Please create a custom field for Epic Name and provide it in the fields parameter as { \"customfield_Id\" : \"epic_name\" }")
+ error_text = "{}. {}".format(
+ error_text,
+ 'Please create a custom field for Epic Name and provide it in the fields parameter as { "customfield_Id" : "epic_name" }',
+ )
return result_object.set_status(phantom.APP_ERROR, "{0}. Message: {1}".format(message, error_text))
@@ -354,22 +356,16 @@ def _extract_err_msg_from_jira_exc_text(self, jira_exc) -> str:
# Try to parse the HTML content of the error in majority situations and if it fails to parse
# the error response as HTML, then, return the raw error text to ensure that the error text
# is not getting dropped from this point
- self.debug_print(
- "Jira error details available in exception, parsing it with "
- "BeautifulSoup."
- )
+ self.debug_print("Jira error details available in exception, parsing it with " "BeautifulSoup.")
try:
soup = BeautifulSoup(error_text, "html.parser")
error_text = soup.text
- split_lines = error_text.split('\n')
+ split_lines = error_text.split("\n")
split_lines = [x.strip() for x in split_lines if x.strip()]
- error_text = '\n'.join(split_lines)
+ error_text = "\n".join(split_lines)
except Exception as parsing_exception:
try:
- error_text = (
- f"Cannot parse error details. Unparsed error: "
- f"{error_text}. Parsing exception: {parsing_exception}"
- )
+ error_text = f"Cannot parse error details. Unparsed error: " f"{error_text}. Parsing exception: {parsing_exception}"
except Exception as ex:
error_text = (
f"Unable to parse the details of the error received "
@@ -385,14 +381,12 @@ def _extract_err_msg_from_jira_exc_response(self, jira_exc) -> str:
jira_error_messages: list[str] = response_content.get(JIRA_RESPONSE_ERROR_MESSAGES_KEY, [])
jira_errors: dict[str, str] = response_content.get(JIRA_RESPONSE_ERRORS_KEY, {})
all_jira_error_messages: list[str] = jira_error_messages + [
- f"{field_name}: {error_details}"
- for field_name, error_details in jira_errors.items()
+ f"{field_name}: {error_details}" for field_name, error_details in jira_errors.items()
]
error_text = (
"\n".join(all_jira_error_messages)
if all_jira_error_messages
- else "Unable to parse the details of the error received "
- "in the output response"
+ else "Unable to parse the details of the error received " "in the output response"
)
except Exception as e:
@@ -402,11 +396,11 @@ def _extract_err_msg_from_jira_exc_response(self, jira_exc) -> str:
def _create_jira_object(self, action_result):
- if not self._verify_cert and 'REQUESTS_CA_BUNDLE' in os.environ:
- del os.environ['REQUESTS_CA_BUNDLE']
+ if not self._verify_cert and "REQUESTS_CA_BUNDLE" in os.environ:
+ del os.environ["REQUESTS_CA_BUNDLE"]
# create the options dictionary
- options = {'server': self._base_url, 'verify': self._verify_cert}
+ options = {"server": self._base_url, "verify": self._verify_cert}
signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(JIRA_START_TIMEOUT)
@@ -480,11 +474,11 @@ def _list_projects(self, param):
for project in projects:
data = action_result.add_data({})
- if hasattr(project, 'key'):
+ if hasattr(project, "key"):
data[JIRA_JSON_PROJECT_KEY] = project.key
- if hasattr(project, 'id'):
+ if hasattr(project, "id"):
data[JIRA_JSON_PROJECT_ID] = project.id
- if hasattr(project, 'name'):
+ if hasattr(project, "name"):
data[JIRA_JSON_PROJECT_NAME] = project.name
action_result.set_summary({JIRA_TOTAL_PROJECTS: len(projects)})
@@ -500,18 +494,25 @@ def _get_custom_fields_for_issue(self, issue_id, action_result):
error_text = "Unable to get edit meta info about the issue. {}".format(error_message)
return action_result.set_status(phantom.APP_ERROR, error_text), None, None
- fields_meta = edit_meta.get('fields')
+ fields_meta = edit_meta.get("fields")
if not fields_meta:
- return (action_result.set_status(phantom.APP_ERROR,
- "Got an empty response to the 'editmeta' REST endpoint. This may be caused by a jira permission problem"), None, None)
+ return (
+ action_result.set_status(
+ phantom.APP_ERROR, "Got an empty response to the 'editmeta' REST endpoint. This may be caused by a jira permission problem"
+ ),
+ None,
+ None,
+ )
# create an array of custom fields
try:
- custom_fields = [x for x in fields_meta if ('customfield' in x)]
+ custom_fields = [x for x in fields_meta if ("customfield" in x)]
except Exception as e:
error_message = self._get_error_message_from_exception(e)
error_text = "Unable to parse edit meta info to extract custom fields. \
- {}".format(error_message)
+ {}".format(
+ error_message
+ )
return action_result.set_status(phantom.APP_ERROR, error_text), None, None
return phantom.APP_SUCCESS, custom_fields, fields_meta
@@ -550,7 +551,7 @@ def _replace_custom_name_with_id(self, input_fields, custom_name_to_id, action_r
def _get_update_fields(self, param, issue_id, action_result, key=JIRA_JSON_UPDATE_FIELDS):
- update_fields = param.get(key, '')
+ update_fields = param.get(key, "")
# update_fields is an optional field
if not update_fields:
@@ -564,7 +565,7 @@ def _get_update_fields(self, param, issue_id, action_result, key=JIRA_JSON_UPDAT
error_fields_json_parse = JIRA_ERROR_FIELDS_JSON_PARSE.format(field_name=JIRA_JSON_UPDATE_FIELDS)
error_text = "{0} {1}".format(error_fields_json_parse, error_message)
- return action_result.set_status(phantom.APP_ERROR, error_text.replace('{', '(').replace('}', ')')), None
+ return action_result.set_status(phantom.APP_ERROR, error_text.replace("{", "(").replace("}", ")")), None
if not isinstance(update_fields, dict):
return action_result.set_status(phantom.APP_ERROR, "Please provide a valid JSON formatted dictionary"), None
@@ -583,19 +584,19 @@ def _get_update_fields(self, param, issue_id, action_result, key=JIRA_JSON_UPDAT
ret_val = True
- fields = update_fields.get('fields')
+ fields = update_fields.get("fields")
if fields:
status, fields = self._replace_custom_name_with_id(fields, custom_name_to_id, action_result)
- del update_fields_copy['fields']
+ del update_fields_copy["fields"]
ret_val &= status
if not status:
fields = None
- update = update_fields.get('update')
+ update = update_fields.get("update")
if update:
status, update = self._replace_custom_name_with_id(update, custom_name_to_id, action_result)
- del update_fields_copy['update']
+ del update_fields_copy["update"]
ret_val &= status
if not status:
update = None
@@ -612,9 +613,9 @@ def _get_update_fields(self, param, issue_id, action_result, key=JIRA_JSON_UPDAT
update_fields_to_ret = {}
if fields:
- update_fields_to_ret['fields'] = fields
+ update_fields_to_ret["fields"] = fields
if update:
- update_fields_to_ret['update'] = update
+ update_fields_to_ret["update"] = update
if keys:
update_fields_to_ret.update(keys)
@@ -639,8 +640,8 @@ def _set_ticket_status(self, param): # noqa: C901
kwargs = {}
issue_id = param[JIRA_JSON_ID]
- param_update_fields = param.get(JIRA_JSON_UPDATE_FIELDS, '')
- time_spent = param.get(JIRA_JSON_TIMESPENT, '')
+ param_update_fields = param.get(JIRA_JSON_UPDATE_FIELDS, "")
+ time_spent = param.get(JIRA_JSON_TIMESPENT, "")
try:
issue = self._jira.issue(issue_id)
@@ -668,9 +669,9 @@ def _set_ticket_status(self, param): # noqa: C901
if not update_result:
error_message = "Error occurred while updating the ticket: Failed to update fields"
- return action_result.set_status(phantom.APP_ERROR, '{0} Error message: {1}'.format(error_message, action_result.get_message()))
+ return action_result.set_status(phantom.APP_ERROR, "{0} Error message: {1}".format(error_message, action_result.get_message()))
- kwargs.update({'issue': issue_id})
+ kwargs.update({"issue": issue_id})
status_to_set = param[JIRA_JSON_STATUS]
@@ -678,28 +679,30 @@ def _set_ticket_status(self, param): # noqa: C901
transitions = self._jira.transitions(issue_id)
try:
- transition_info = [x for x in transitions if x['name'] == status_to_set]
+ transition_info = [x for x in transitions if x["name"] == status_to_set]
except Exception:
- return action_result.set_status(phantom.APP_ERROR,
- "Unable to parse response from server while trying to get information about status values")
+ return action_result.set_status(
+ phantom.APP_ERROR, "Unable to parse response from server while trying to get information about status values"
+ )
if not transition_info:
message = JIRA_ERROR_ISSUE_VALID_TRANSITIONS
valid_transitions = self._get_list_string(transitions)
if valid_transitions:
- valid_transitions = ', '.join(valid_transitions)
+ valid_transitions = ", ".join(valid_transitions)
message = "{0}. Valid status value(s): {1}".format(message, valid_transitions)
return action_result.set_status(phantom.APP_ERROR, message)
try:
- transition_id = transition_info[0]['id']
+ transition_id = transition_info[0]["id"]
except Exception:
- return action_result.set_status(phantom.APP_ERROR,
- "Unable to parse response from server while trying to get information about status values")
+ return action_result.set_status(
+ phantom.APP_ERROR, "Unable to parse response from server while trying to get information about status values"
+ )
- kwargs.update({'transition': transition_id})
+ kwargs.update({"transition": transition_id})
- resolution_to_set = param.get(JIRA_JSON_RESOLUTION, '')
+ resolution_to_set = param.get(JIRA_JSON_RESOLUTION, "")
if resolution_to_set:
@@ -709,25 +712,27 @@ def _set_ticket_status(self, param): # noqa: C901
try:
resolution_info = [x for x in resolutions if x.name == resolution_to_set]
except Exception:
- return action_result.set_status(phantom.APP_ERROR,
- "Unable to parse response from server while trying to get resolution about status values")
+ return action_result.set_status(
+ phantom.APP_ERROR, "Unable to parse response from server while trying to get resolution about status values"
+ )
if not resolution_info:
message = JIRA_ERROR_ISSUE_VALID_RESOLUTION
valid_resolutions = self._get_list_string(resolutions)
if valid_resolutions:
- valid_resolutions = ', '.join(valid_resolutions)
+ valid_resolutions = ", ".join(valid_resolutions)
message = "{0} Valid resolution value(s): {1}".format(message, valid_resolutions)
return action_result.set_status(phantom.APP_ERROR, message)
try:
resolution_id = resolution_info[0].id
except Exception:
- return action_result.set_status(phantom.APP_ERROR,
- "Unable to parse response from server while trying to get information about resolution values")
+ return action_result.set_status(
+ phantom.APP_ERROR, "Unable to parse response from server while trying to get information about resolution values"
+ )
if resolution_to_set:
- kwargs.update({'fields': {'resolution': {'id': resolution_id}}})
+ kwargs.update({"fields": {"resolution": {"id": resolution_id}}})
# So far, so good, try to now set the values
try:
@@ -742,7 +747,7 @@ def _set_ticket_status(self, param): # noqa: C901
message += ". The combination of status and resolution could be invalid"
return self._set_jira_error(action_result, message, e)
- comment = param.get(JIRA_JSON_COMMENT, '')
+ comment = param.get(JIRA_JSON_COMMENT, "")
# to add comment to the issue if present
comment_failure_message = ""
@@ -765,8 +770,10 @@ def _set_ticket_status(self, param): # noqa: C901
if JIRA_ERROR_FETCH_CUSTOM_FIELDS not in error_message:
return action_result.get_status()
- return action_result.set_status(phantom.APP_SUCCESS,
- "The status is updated successfully" + (". NOTE: {0}".format(comment_failure_message) if comment_failure_message else ""))
+ return action_result.set_status(
+ phantom.APP_SUCCESS,
+ "The status is updated successfully" + (". NOTE: {0}".format(comment_failure_message) if comment_failure_message else ""),
+ )
def _get_list_string(self, obj_list):
@@ -775,19 +782,19 @@ def _get_list_string(self, obj_list):
for item in obj_list:
- name = ''
+ name = ""
- if hasattr(item, 'raw'):
+ if hasattr(item, "raw"):
name = item.name
else:
- name = item.get('name')
+ name = item.get("name")
if name:
# ret_string += "
{0}".format(name)
ret_list.append(name)
# if (ret_string):
- # ret_string = "".format(ret_string)
+ # ret_string = "".format(ret_string)
# return ret_string
return ret_list
@@ -808,8 +815,8 @@ def _update_ticket(self, param):
issue_id = param[JIRA_JSON_ID]
- attachment = param.get(JIRA_JSON_ATTACHMENT, '')
- param_update_fields = param.get(JIRA_JSON_UPDATE_FIELDS, '')
+ attachment = param.get(JIRA_JSON_ATTACHMENT, "")
+ param_update_fields = param.get(JIRA_JSON_UPDATE_FIELDS, "")
if (not attachment) and (not param_update_fields):
return action_result.set_status(phantom.APP_ERROR, JIRA_ERROR_UPDATE_NO_PARAM)
@@ -868,7 +875,7 @@ def _update_ticket(self, param):
error_message += "Attachment successfully added. " if attach_result else "Failed to add attachment. "
if param_update_fields:
error_message += "Fields successfully updated." if update_result else "Failed to update fields."
- return action_result.set_status(phantom.APP_ERROR, '{0} Error message: {1}'.format(error_message, action_result.get_message()))
+ return action_result.set_status(phantom.APP_ERROR, "{0} Error message: {1}".format(error_message, action_result.get_message()))
return action_result.set_status(phantom.APP_SUCCESS, JIRA_SUCCESS_TICKET_UPDATED)
@@ -923,9 +930,9 @@ def _create_ticket(self, param): # noqa: C901
summary = param[JIRA_JSON_SUMMARY]
issue_type = param[JIRA_JSON_ISSUE_TYPE]
- description = param.get(JIRA_JSON_DESCRIPTION, '')
- priority = param.get(JIRA_JSON_ISSUE_PRIORITY, '')
- attachment = param.get(JIRA_JSON_ATTACHMENT, '')
+ description = param.get(JIRA_JSON_DESCRIPTION, "")
+ priority = param.get(JIRA_JSON_ISSUE_PRIORITY, "")
+ attachment = param.get(JIRA_JSON_ATTACHMENT, "")
assignee_username = param.get(JIRA_JSON_ISSUE_ASSIGNEE)
@@ -944,31 +951,31 @@ def _create_ticket(self, param): # noqa: C901
error_fields_json_parse = JIRA_ERROR_FIELDS_JSON_PARSE.format(field_name=JIRA_JSON_FIELDS)
error_text = "{0} {1}".format(error_fields_json_parse, error_message)
- return action_result.set_status(phantom.APP_ERROR, error_text.replace('{', '(').replace('}', ')'))
+ return action_result.set_status(phantom.APP_ERROR, error_text.replace("{", "(").replace("}", ")"))
- if 'fields' in fields:
+ if "fields" in fields:
if len(list(fields.keys())) > 1:
return action_result.set_status(phantom.APP_ERROR, JIRA_ERROR_INPUT_FIELDS_NOT_THE_ONLY_ONE)
- fields = fields['fields']
+ fields = fields["fields"]
# update value in fields if not present in the json
- if project_key and ('project' not in fields):
- fields['project'] = {}
- fields['project']['key'] = project_key
+ if project_key and ("project" not in fields):
+ fields["project"] = {}
+ fields["project"]["key"] = project_key
- if summary and ('summary' not in fields):
- fields['summary'] = summary
+ if summary and ("summary" not in fields):
+ fields["summary"] = summary
- if description and ('description' not in fields):
- fields['description'] = description
+ if description and ("description" not in fields):
+ fields["description"] = description
- if issue_type and ('issuetype' not in fields):
- fields['issuetype'] = {}
- fields['issuetype']['name'] = issue_type
+ if issue_type and ("issuetype" not in fields):
+ fields["issuetype"] = {}
+ fields["issuetype"]["name"] = issue_type
- if priority and ('priority' not in fields):
- fields['priority'] = {}
- fields['priority']['name'] = priority
+ if priority and ("priority" not in fields):
+ fields["priority"] = {}
+ fields["priority"]["name"] = priority
self.debug_print("Creating the ticket")
# Create JIRA ticket
@@ -976,8 +983,7 @@ def _create_ticket(self, param): # noqa: C901
new_issue = self._jira.create_issue(fields=fields)
except KeyError as ke:
error_message = self._get_error_message_from_exception(ke)
- error_text = "{0}. Missing required key. {1}".format(
- JIRA_ERROR_CREATE_TICKET_FAILED, error_message)
+ error_text = "{0}. Missing required key. {1}".format(JIRA_ERROR_CREATE_TICKET_FAILED, error_message)
return action_result.set_status(phantom.APP_ERROR, error_text)
except Exception as e:
@@ -1002,10 +1008,8 @@ def _create_ticket(self, param): # noqa: C901
assignee_status = JIRA_ERROR_TICKET_ASSIGNMENT_FAILED.format(assignee_username, error_text)
else:
self.debug_print("Assigning to user for Jira cloud")
- endpoint = 'issue/{0}/assignee'.format(str(new_issue))
- payload = {
- "accountId": assignee_account_id
- }
+ endpoint = "issue/{0}/assignee".format(str(new_issue))
+ payload = {"accountId": assignee_account_id}
try:
ret_val, _ = self._make_rest_call(endpoint, action_result, data=json.dumps(payload), method="put")
except Exception as e:
@@ -1030,11 +1034,11 @@ def _create_ticket(self, param): # noqa: C901
if assignee_status:
status_message = "{} {}".format(status_message, assignee_status)
- result_data['assign_error'] = assignee_status
+ result_data["assign_error"] = assignee_status
if attachment_status:
status_message = "{} {}".format(status_message, attachment_status)
- result_data['attach_error'] = attachment_status
+ result_data["attach_error"] = attachment_status
action_result.set_status(phantom.APP_SUCCESS, status_message)
return action_result.get_status()
@@ -1060,17 +1064,16 @@ def _list_tickets(self, param):
action_query = param.get(JIRA_JSON_QUERY, "")
if not project_key and not action_query:
- error_text = JIRA_ERROR_LIST_TICKETS_FAILED + \
- ". Please provide either project_key or query. Both fields cannot be empty"
+ error_text = JIRA_ERROR_LIST_TICKETS_FAILED + ". Please provide either project_key or query. Both fields cannot be empty"
return action_result.set_status(phantom.APP_ERROR, error_text)
start_index = param.get(JIRA_JSON_START_INDEX, DEFAULT_START_INDEX)
- start_index = self._validate_integers(action_result, start_index, 'start_index action', allow_zero=True)
+ start_index = self._validate_integers(action_result, start_index, "start_index action", allow_zero=True)
if start_index is None:
return action_result.get_status()
limit = param.get(JIRA_JSON_MAX_RESULTS, DEFAULT_MAX_VALUE)
- limit = self._validate_integers(action_result, limit, 'max_results action')
+ limit = self._validate_integers(action_result, limit, "max_results action")
if limit is None:
return action_result.get_status()
@@ -1091,7 +1094,7 @@ def _list_tickets(self, param):
ret_val = self._parse_issue_data(issue, issue_ar)
if phantom.is_fail(ret_val):
- self.debug_print('Error occurred while parsing the issue data: {0}. Error: {1}'.format(issue.key, issue_ar.get_message()))
+ self.debug_print("Error occurred while parsing the issue data: {0}. Error: {1}".format(issue.key, issue_ar.get_message()))
data = issue_ar.get_data()
action_result.update_data(data)
@@ -1113,8 +1116,9 @@ def _username_paginator(self, username, action_result, start_index=0, limit=None
return None
if users is None:
- action_result.set_status(phantom.APP_ERROR,
- 'Unknown error occurred while fetching list of users using pagination for Jira on-prem')
+ action_result.set_status(
+ phantom.APP_ERROR, "Unknown error occurred while fetching list of users using pagination for Jira on-prem"
+ )
return None
users_list.extend(users)
@@ -1134,29 +1138,33 @@ def _display_name_paginator(self, display_name, action_result, start_index=0, li
users_list = list()
param = dict()
- param.update({
- "includeActive": True,
- "includeInactive": False,
- "maxResults": DEFAULT_MAX_RESULTS_PER_PAGE,
- "startAt": start_index,
- "query": "displayName=\"{0}\"".format(display_name)
- })
+ param.update(
+ {
+ "includeActive": True,
+ "includeInactive": False,
+ "maxResults": DEFAULT_MAX_RESULTS_PER_PAGE,
+ "startAt": start_index,
+ "query": 'displayName="{0}"'.format(display_name),
+ }
+ )
while True:
try:
ret_val, users = self._make_rest_call("user/search", action_result, params=param)
except Exception as e:
error_text = self._get_error_message_from_exception(e)
- action_result.set_status(phantom.APP_ERROR,
- "Error occurred while fetching the list of users for Jira cloud. Error: {0}".format(error_text))
+ action_result.set_status(
+ phantom.APP_ERROR, "Error occurred while fetching the list of users for Jira cloud. Error: {0}".format(error_text)
+ )
return None
if phantom.is_fail(ret_val):
return None
if users is None:
- action_result.set_status(phantom.APP_ERROR,
- 'Unknown error occurred while fetching list of users using pagination for Jira cloud')
+ action_result.set_status(
+ phantom.APP_ERROR, "Unknown error occurred while fetching list of users using pagination for Jira cloud"
+ )
return None
users_list.extend(users)
@@ -1195,7 +1203,7 @@ def _handle_search_users(self, param):
start_index = 0
limit = param.get(JIRA_JSON_MAX_RESULTS, DEFAULT_MAX_VALUE)
- limit = self._validate_integers(action_result, limit, 'max_results action')
+ limit = self._validate_integers(action_result, limit, "max_results action")
if limit is None:
return action_result.get_status()
@@ -1215,8 +1223,7 @@ def _handle_search_users(self, param):
action_result.add_data(user.raw)
except Exception as e:
error_text = self._get_error_message_from_exception(e)
- return action_result.set_status(phantom.APP_ERROR, "Error occurred while processing fetched response. Error: {0}".format(
- error_text))
+ return action_result.set_status(phantom.APP_ERROR, "Error occurred while processing fetched response. Error: {0}".format(error_text))
summary = action_result.update_summary({})
summary[JIRA_TOTAL_USERS] = action_result.get_data_size()
@@ -1224,7 +1231,7 @@ def _handle_search_users(self, param):
return action_result.set_status(phantom.APP_SUCCESS)
def _add_comment_for_set_status(self, issue_id, issue, comment, action_result):
- """ This method is used to add comment when we add comment while set status action.
+ """This method is used to add comment when we add comment while set status action.
:rtype: string
:param issue_id: Issue ID
@@ -1237,8 +1244,8 @@ def _add_comment_for_set_status(self, issue_id, issue, comment, action_result):
return action_result.get_status()
update_body = {"update": {"comment": [{"add": {}}]}}
- comment_body = update_body['update']['comment'][0]['add']
- comment_body['body'] = comment
+ comment_body = update_body["update"]["comment"][0]["add"]
+ comment_body["body"] = comment
ret_val, update_fields = self._get_update_fields({"update_fields": json.dumps(update_body)}, issue_id, action_result)
@@ -1281,7 +1288,7 @@ def _add_comment(self, param):
return action_result.set_status(phantom.APP_ERROR, "Unable to find ticket info. Please make sure the issue exists")
try:
- self._jira.add_comment(issue_id, body, is_internal=param.get('internal', False))
+ self._jira.add_comment(issue_id, body, is_internal=param.get("internal", False))
except Exception as e:
return self._set_jira_error(action_result, "Failed to add the comment", e)
@@ -1289,19 +1296,19 @@ def _add_comment(self, param):
def _add_update_fields(self, issue, fields, action_result):
- if 'fields' in fields:
+ if "fields" in fields:
self.save_progress("Modifying the ticket using the 'fields' dictionary")
try:
- issue.update(fields=fields['fields'])
- del fields['fields']
+ issue.update(fields=fields["fields"])
+ del fields["fields"]
except Exception as e:
return self._set_jira_error(action_result, JIRA_ERROR_UPDATE_FAILED, e)
- if 'update' in fields:
+ if "update" in fields:
self.save_progress("Modifying the ticket with the 'update' dictionary")
try:
- issue.update(update=fields['update'])
- del fields['update']
+ issue.update(update=fields["update"])
+ del fields["update"]
except Exception as e:
return self._set_jira_error(action_result, JIRA_ERROR_UPDATE_FAILED, e)
@@ -1339,27 +1346,32 @@ def _add_attachment(self, issue, vault_id):
file_meta = meta
break
else:
- self.debug_print("Unable to find a file for the vault ID: '{0}' in the container ID: '{1}'".format(
- vault_id, self.get_container_id()))
+ self.debug_print(
+ "Unable to find a file for the vault ID: '{0}' in the container ID: '{1}'".format(vault_id, self.get_container_id())
+ )
except Exception:
- self.debug_print("Error occurred while finding a file for the vault ID: '{0}' in the container ID: '{1}'".format(
- vault_id, self.get_container_id()))
+ self.debug_print(
+ "Error occurred while finding a file for the vault ID: '{0}' in the container ID: '{1}'".format(
+ vault_id, self.get_container_id()
+ )
+ )
self.debug_print("Considering the first file as the required file")
file_meta = vault_meta[0]
if not file_meta:
- self.debug_print("Unable to find a file for the vault ID: '{0}' in the container ID: '{1}'".format(
- vault_id, self.get_container_id()))
+ self.debug_print(
+ "Unable to find a file for the vault ID: '{0}' in the container ID: '{1}'".format(vault_id, self.get_container_id())
+ )
self.debug_print("Considering the first file as the required file")
file_meta = vault_meta[0]
try:
- path = list(vault_meta)[0].get('path')
+ path = list(vault_meta)[0].get("path")
filename = file_meta["name"]
try:
self.debug_print("First attempt to add attachment to the given Jira ticket ID")
- with open(path, 'rb') as f:
+ with open(path, "rb") as f:
self._jira.add_attachment(issue=issue, attachment=f, filename=filename)
except Exception as e:
# Remove this block of exception handling once the PAPP-9898 bug is fixed
@@ -1369,7 +1381,7 @@ def _add_attachment(self, issue, vault_id):
# into the add_attachment() method of Jira SDK, it throws 500 Internal Server Error
# and it will fail to add attachment on the Jira ticket.
self.debug_print("Try to remove non-ASCII Unicode chars from the filename")
- modified_filename = filename.encode('ascii', 'ignore').decode('ascii')
+ modified_filename = filename.encode("ascii", "ignore").decode("ascii")
modified_filename = modified_filename.replace("\r", " ").replace("\n", " ")
except Exception:
self.debug_print("Failed to remove non-ASCII Unicode chars from the filename")
@@ -1379,7 +1391,7 @@ def _add_attachment(self, issue, vault_id):
if len(modified_filename) < len(filename):
self.debug_print("Add prefix to filename after removing non-ASCII Unicode chars")
filename = "FILENAME_ASCII_{}".format(modified_filename)
- with open(path, 'rb') as f:
+ with open(path, "rb") as f:
self._jira.add_attachment(issue=issue, attachment=f, filename=filename)
else:
self.debug_print("Raise the same exception as filename doesn't have Unicode chars or unable to remove Unicode chars")
@@ -1402,13 +1414,12 @@ def _parse_issue_data(self, issue, action_result):
data[JIRA_JSON_ID] = issue.id
issue_dict = issue.raw
- if 'fields' in issue_dict:
- data['fields'] = issue_dict['fields']
+ if "fields" in issue_dict:
+ data["fields"] = issue_dict["fields"]
data = action_result.add_data(data)
except Exception:
- return action_result.set_status(phantom.APP_ERROR,
- "Unable to parse the response containing issue details from the server")
+ return action_result.set_status(phantom.APP_ERROR, "Unable to parse the response containing issue details from the server")
try:
data[JIRA_JSON_PRIORITY] = issue.fields.priority.name
@@ -1450,7 +1461,7 @@ def _parse_issue_data(self, issue, action_result):
except Exception:
pass
- if not data.get('fields'):
+ if not data.get("fields"):
# No fields, so nothing more to do, we've already added the data
return phantom.APP_SUCCESS
@@ -1500,7 +1511,8 @@ def _get_ticket(self, param):
error_message = error_message
try:
message = "The ticket has been retrieved successfully{0}".format(
- ". Error while fetching custom fields: {0}".format(error_message) if error_message else "")
+ ". Error while fetching custom fields: {0}".format(error_message) if error_message else ""
+ )
except Exception:
message = "The ticket has been retrieved successfully"
@@ -1509,7 +1521,8 @@ def _get_ticket(self, param):
def _get_container_id(self, issue_key):
url = '{0}rest/container?_filter_source_data_identifier="{1}"&_filter_asset={2}'.format(
- self.get_phantom_base_url(), issue_key, self.get_asset_id())
+ self.get_phantom_base_url(), issue_key, self.get_asset_id()
+ )
try:
r = requests.get(url, verify=False) # nosemgrep
@@ -1518,12 +1531,12 @@ def _get_container_id(self, issue_key):
self.debug_print("Unable to query JIRA ticket container: ", e)
return None
- if resp_json.get('count', 0) <= 0:
+ if resp_json.get("count", 0) <= 0:
self.debug_print("No container matched")
return None
try:
- container_id = resp_json.get('data', [])[0]['id']
+ container_id = resp_json.get("data", [])[0]["id"]
except Exception as e:
self.debug_print("Container results are not proper: ", e)
return None
@@ -1533,7 +1546,8 @@ def _get_container_id(self, issue_key):
def _get_artifact_id(self, sdi, container_id, full_artifact=False):
url = '{0}rest/artifact?_filter_source_data_identifier="{1}"&_filter_container_id={2}&sort=id&order=desc'.format(
- self.get_phantom_base_url(), sdi, container_id)
+ self.get_phantom_base_url(), sdi, container_id
+ )
try:
r = requests.get(url, verify=False) # nosemgrep
@@ -1542,16 +1556,16 @@ def _get_artifact_id(self, sdi, container_id, full_artifact=False):
self.debug_print("Unable to query JIRA artifact: ", e)
return None
- if resp_json.get('count', 0) <= 0:
+ if resp_json.get("count", 0) <= 0:
self.debug_print("No artifact matched")
return None
try:
if full_artifact:
- previous_artifacts_list = resp_json.get('data', [])
+ previous_artifacts_list = resp_json.get("data", [])
return previous_artifacts_list[0]
else:
- return resp_json.get('data', [])[0]['id']
+ return resp_json.get("data", [])[0]["id"]
except Exception as e:
self.debug_print("Artifact results are not proper: ", e)
return None
@@ -1573,9 +1587,9 @@ def _get_custom_fields_id_name_map(self, issue_id, action_result, id_to_name=Tru
return custom_fields_id_name_map
if id_to_name:
- return dict([(x, fields_meta[x]['name']) for x in custom_fields_info])
+ return dict([(x, fields_meta[x]["name"]) for x in custom_fields_info])
else:
- return dict([(fields_meta[x]['name'], x) for x in custom_fields_info])
+ return dict([(fields_meta[x]["name"], x) for x in custom_fields_info])
def _fetch_fields_by_replacing_custom_fields_id_to_name(self, issue, action_result):
@@ -1583,7 +1597,7 @@ def _fetch_fields_by_replacing_custom_fields_id_to_name(self, issue, action_resu
try:
issue_dict = issue.raw
- fields = issue_dict.get('fields')
+ fields = issue_dict.get("fields")
except Exception:
action_result.set_status(phantom.APP_ERROR, "Error occurred while fetching the fields from the issue: {0}".format(issue.key))
return None
@@ -1602,13 +1616,13 @@ def _build_fields_artifact(self, issue, container_id, action_result):
artifact_json = {}
- artifact_json['container_id'] = container_id
- artifact_json['source_data_identifier'] = issue.key
+ artifact_json["container_id"] = container_id
+ artifact_json["source_data_identifier"] = issue.key
try:
- artifact_json['label'] = issue.fields.issuetype.name
+ artifact_json["label"] = issue.fields.issuetype.name
except Exception:
- artifact_json['label'] = "issue"
+ artifact_json["label"] = "issue"
artifact_cef = {}
@@ -1669,7 +1683,7 @@ def _build_fields_artifact(self, issue, container_id, action_result):
except Exception:
pass
- artifact_json['cef'] = artifact_cef
+ artifact_json["cef"] = artifact_cef
return artifact_json
@@ -1696,7 +1710,7 @@ def _download_file(self, url, local_file_path):
block_size = 512 * 1024
try:
- with open(local_file_path, 'wb') as file_handle:
+ with open(local_file_path, "wb") as file_handle:
for chunk in r.iter_content(chunk_size=block_size):
if chunk:
bytes_downloaded += len(chunk)
@@ -1709,14 +1723,14 @@ def _download_file(self, url, local_file_path):
self.debug_print("Error downloading file: {}".format(error_text))
return phantom.APP_ERROR
- os.chmod(local_file_path, 0o660) # nosemgrep
+ os.chmod(local_file_path, 0o660) # nosemgrep
return phantom.APP_SUCCESS
def _handle_attachment(self, attachment, container_id, artifact_list, action_result):
try:
- if hasattr(Vault, 'get_vault_tmp_dir'):
+ if hasattr(Vault, "get_vault_tmp_dir"):
tmp = tempfile.NamedTemporaryFile(dir=Vault.get_vault_tmp_dir(), delete=False)
else:
local_dir = os.path.join(paths.PHANTOM_VAULT, "tmp")
@@ -1737,38 +1751,41 @@ def _handle_attachment(self, attachment, container_id, artifact_list, action_res
artifact_json = {}
- artifact_json['name'] = 'attachment - {0}'.format(filename)
- artifact_json['label'] = 'attachment'
- artifact_json['container_id'] = container_id
- artifact_json['source_data_identifier'] = attachment.id
+ artifact_json["name"] = "attachment - {0}".format(filename)
+ artifact_json["label"] = "attachment"
+ artifact_json["container_id"] = container_id
+ artifact_json["source_data_identifier"] = attachment.id
artifact_cef = {}
- artifact_cef['size'] = attachment.size
- artifact_cef['created'] = attachment.created
- artifact_cef['filename'] = filename
- artifact_cef['mimeType'] = attachment.mimeType
+ artifact_cef["size"] = attachment.size
+ artifact_cef["created"] = attachment.created
+ artifact_cef["filename"] = filename
+ artifact_cef["mimeType"] = attachment.mimeType
try:
- artifact_cef['author'] = attachment.author.name
- artifact_cef['author_account_id'] = None
- artifact_cef['is_on_prem'] = True
+ artifact_cef["author"] = attachment.author.name
+ artifact_cef["author_account_id"] = None
+ artifact_cef["is_on_prem"] = True
except Exception:
- self.debug_print("Error occurred while fetching author name as server is Jira cloud. \
- So try to fetch author display name and account ID")
- artifact_cef['author_account_id'] = attachment.author.accountId
- artifact_cef['author'] = attachment.author.displayName
- artifact_cef['is_on_prem'] = False
+ self.debug_print(
+ "Error occurred while fetching author name as server is Jira cloud. \
+ So try to fetch author display name and account ID"
+ )
+ artifact_cef["author_account_id"] = attachment.author.accountId
+ artifact_cef["author"] = attachment.author.displayName
+ artifact_cef["is_on_prem"] = False
- artifact_cef['vault_id'] = vault_id
+ artifact_cef["vault_id"] = vault_id
- artifact_json['cef'] = artifact_cef
+ artifact_json["cef"] = artifact_cef
artifact_list.append(artifact_json)
except Exception as e:
error_text = self._get_error_message_from_exception(e)
- action_result.set_status(phantom.APP_ERROR,
- "Error occurred while creation of the attachment artifact. Error message: {0}".format(error_text))
+ action_result.set_status(
+ phantom.APP_ERROR, "Error occurred while creation of the attachment artifact. Error message: {0}".format(error_text)
+ )
return phantom.APP_ERROR
return phantom.APP_SUCCESS
@@ -1785,37 +1802,40 @@ def _handle_comment(self, comment, container_id, base_name, artifact_list, actio
updateAuthor_account_id = None
is_on_prem = True
except Exception:
- self.debug_print("Error occurred while fetching author name as server is Jira cloud. \
- So try to fetch author display name and account ID")
+ self.debug_print(
+ "Error occurred while fetching author name as server is Jira cloud. \
+ So try to fetch author display name and account ID"
+ )
author = comment.author.displayName
updateAuthor = comment.updateAuthor.displayName
author_account_id = comment.author.accountId
updateAuthor_account_id = comment.updateAuthor.accountId
is_on_prem = False
- artifact_json['name'] = '{0} by {1}'.format(base_name, author)
- artifact_json['label'] = 'comment'
- artifact_json['container_id'] = container_id
- artifact_json['source_data_identifier'] = comment.id
+ artifact_json["name"] = "{0} by {1}".format(base_name, author)
+ artifact_json["label"] = "comment"
+ artifact_json["container_id"] = container_id
+ artifact_json["source_data_identifier"] = comment.id
artifact_cef = {}
- artifact_cef['body'] = comment.body
- artifact_cef['created'] = comment.created
- artifact_cef['updated'] = comment.updated
- artifact_cef['is_on_prem'] = is_on_prem
- artifact_cef['author'] = author
- artifact_cef['author_account_id'] = author_account_id
- artifact_cef['updateAuthor'] = updateAuthor
- artifact_cef['updateAuthor_account_id'] = updateAuthor_account_id
+ artifact_cef["body"] = comment.body
+ artifact_cef["created"] = comment.created
+ artifact_cef["updated"] = comment.updated
+ artifact_cef["is_on_prem"] = is_on_prem
+ artifact_cef["author"] = author
+ artifact_cef["author_account_id"] = author_account_id
+ artifact_cef["updateAuthor"] = updateAuthor
+ artifact_cef["updateAuthor_account_id"] = updateAuthor_account_id
- artifact_json['cef'] = artifact_cef
+ artifact_json["cef"] = artifact_cef
artifact_list.append(artifact_json)
except Exception as e:
error_text = self._get_error_message_from_exception(e)
- action_result.set_status(phantom.APP_ERROR,
- "Error occurred while creation of the comment artifact. Error message: {0}".format(error_text))
+ action_result.set_status(
+ phantom.APP_ERROR, "Error occurred while creation of the comment artifact. Error message: {0}".format(error_text)
+ )
return phantom.APP_ERROR
return phantom.APP_SUCCESS
@@ -1828,7 +1848,8 @@ def _paginator(self, jql_query, action_result, start_index=0, limit=None, fields
try:
if fields:
issues = self._jira.search_issues(
- jql_str=jql_query, startAt=start_index, maxResults=DEFAULT_MAX_RESULTS_PER_PAGE, fields='updated')
+ jql_str=jql_query, startAt=start_index, maxResults=DEFAULT_MAX_RESULTS_PER_PAGE, fields="updated"
+ )
else:
issues = self._jira.search_issues(jql_str=jql_query, startAt=start_index, maxResults=DEFAULT_MAX_RESULTS_PER_PAGE)
except Exception as e:
@@ -1836,7 +1857,7 @@ def _paginator(self, jql_query, action_result, start_index=0, limit=None, fields
return None
if issues is None:
- action_result.set_status(phantom.APP_ERROR, 'Unknown error occurred while fetching list of tickets (issues) using pagination')
+ action_result.set_status(phantom.APP_ERROR, "Unknown error occurred while fetching list of tickets (issues) using pagination")
return None
issues_list.extend(issues)
@@ -1868,25 +1889,19 @@ def _handle_link_tickets(self, param):
to_issue = param[JIRA_JSON_TO_ID]
link_type = param[JIRA_JSON_LINK_TYPE]
- comment_body = param.get(JIRA_JSON_COMMENT, '')
+ comment_body = param.get(JIRA_JSON_COMMENT, "")
comment_vis_type = param.get(JIRA_JSON_COMMENT_VISIBILITY_TYPE)
comment_vis_value = param.get(JIRA_JSON_COMMENT_VISIBILITY)
comment = None
try:
- UnicodeDammit(link_type).unicode_markup.encode('utf-8')
+ UnicodeDammit(link_type).unicode_markup.encode("utf-8")
except Exception:
return action_result.set_status(phantom.APP_ERROR, "'link_type' action parameter value failed validation. Enter a valid value")
if comment_body:
- comment = {
- "body": comment_body,
- "visibility": {
- "type": comment_vis_type,
- "value": comment_vis_value
- }
- }
+ comment = {"body": comment_body, "visibility": {"type": comment_vis_type, "value": comment_vis_value}}
try:
link = self._jira.create_issue_link(link_type, from_issue, to_issue, comment)
@@ -1922,8 +1937,9 @@ def _handle_add_watcher(self, param):
return action_result.get_status()
if (username and username in watchers) or (account_id and account_id in watchers):
- return action_result.set_status(phantom.APP_SUCCESS,
- "The given user already exists in the watchers list of the issue: {0}".format(issue_id))
+ return action_result.set_status(
+ phantom.APP_SUCCESS, "The given user already exists in the watchers list of the issue: {0}".format(issue_id)
+ )
user = username if username else account_id
@@ -1933,11 +1949,13 @@ def _handle_add_watcher(self, param):
error_text = self._get_error_message_from_exception(e)
error = "Response from the server: {0}".format(error_text)
self.save_progress(error)
- return action_result.set_status(phantom.APP_ERROR,
- "Failed to add the watcher. Please check the provided parameters. {}".format(error))
+ return action_result.set_status(
+ phantom.APP_ERROR, "Failed to add the watcher. Please check the provided parameters. {}".format(error)
+ )
- return action_result.set_status(phantom.APP_SUCCESS,
- "Successfully added the user to the watchers list of the issue ID: {0}".format(issue_id))
+ return action_result.set_status(
+ phantom.APP_SUCCESS, "Successfully added the user to the watchers list of the issue ID: {0}".format(issue_id)
+ )
def get_watchers_list(self, action_result, issue_id, flag=False):
@@ -1950,14 +1968,14 @@ def get_watchers_list(self, action_result, issue_id, flag=False):
return action_result.get_status(), None
watcher_list = list()
- watchers = response.raw.get('watchers', [])
+ watchers = response.raw.get("watchers", [])
try:
for watcher in watchers:
if flag:
- watcher_list.append(watcher['name'])
+ watcher_list.append(watcher["name"])
else:
- watcher_list.append(watcher['accountId'])
+ watcher_list.append(watcher["accountId"])
except KeyError:
return action_result.set_status(phantom.APP_ERROR, JIRA_WATCHERS_ERROR), None
except Exception as e:
@@ -1996,31 +2014,34 @@ def _handle_remove_watcher(self, param):
return action_result.set_status(phantom.APP_ERROR, "No watchers found in the issue ID: {0}".format(issue_id))
if (username and username not in watchers) or (account_id and account_id not in watchers):
- return action_result.set_status(phantom.APP_SUCCESS,
- "The given user is not found in the watchers list of the issue: {0}".format(issue_id))
+ return action_result.set_status(
+ phantom.APP_SUCCESS, "The given user is not found in the watchers list of the issue: {0}".format(issue_id)
+ )
try:
if username:
self._jira.remove_watcher(issue_id, username)
else:
- param = {'accountId': account_id}
- ret_val, _ = self._make_rest_call("issue/{}/watchers".format(issue_id), action_result, params=param, method='delete')
+ param = {"accountId": account_id}
+ ret_val, _ = self._make_rest_call("issue/{}/watchers".format(issue_id), action_result, params=param, method="delete")
if phantom.is_fail(ret_val):
return action_result.get_status()
except Exception as e:
error_text = self._get_error_message_from_exception(e)
ERROR = "Response from the server: {0}".format(error_text)
self.save_progress(ERROR)
- return action_result.set_status(phantom.APP_ERROR,
- "Failed to remove the watcher. Please check the provided parameters. {}".format(ERROR))
+ return action_result.set_status(
+ phantom.APP_ERROR, "Failed to remove the watcher. Please check the provided parameters. {}".format(ERROR)
+ )
- return action_result.set_status(phantom.APP_SUCCESS,
- "Successfully removed the user from the watchers list of the issue ID: {0}".format(issue_id))
+ return action_result.set_status(
+ phantom.APP_SUCCESS, "Successfully removed the user from the watchers list of the issue ID: {0}".format(issue_id)
+ )
def _write_in_file(self, action_result, attachment, full_path, container_id):
try:
- with open(full_path, 'wb') as f:
+ with open(full_path, "wb") as f:
attachment_content = attachment.get()
f.write(attachment_content)
@@ -2029,8 +2050,10 @@ def _write_in_file(self, action_result, attachment, full_path, container_id):
if not success:
error_message = message
- return action_result.set_status(phantom.APP_ERROR, "Error saving file to vault: {}".format(
- error_message if error_message else "Could not save file to vault"))
+ return action_result.set_status(
+ phantom.APP_ERROR,
+ "Error saving file to vault: {}".format(error_message if error_message else "Could not save file to vault"),
+ )
except Exception as e:
error_message = self._get_error_message_from_exception(e)
@@ -2050,10 +2073,10 @@ def _handle_get_attachments(self, param):
if phantom.is_fail(self._create_jira_object(action_result)):
return action_result.get_status()
- ticket_key = param['id']
- container_id = param['container_id']
- extension_filter = param.get('extension_filter', '')
- get_all_attachments = param.get('retrieve_all', False)
+ ticket_key = param["id"]
+ container_id = param["container_id"]
+ extension_filter = param.get("extension_filter", "")
+ get_all_attachments = param.get("retrieve_all", False)
# removing extra comma from an extension filter string
types = [x.strip() for x in extension_filter.split(",")]
@@ -2068,13 +2091,13 @@ def _handle_get_attachments(self, param):
try:
if len(jira_issue.fields.attachment) > 0:
ingest_file_count = 0
- temp_vault_path = Vault.get_vault_tmp_dir().rstrip('/')
+ temp_vault_path = Vault.get_vault_tmp_dir().rstrip("/")
extension_list = None
if extension_filter:
- extension_list = extension_filter.split(',')
- extension_list = [".{}".format(extension.lstrip('.')) for extension in extension_list]
+ extension_list = extension_filter.split(",")
+ extension_list = [".{}".format(extension.lstrip(".")) for extension in extension_list]
elif not get_all_attachments:
error = "Please select retrieve all or pass in a list of extensions to look for. Please check the provided parameters"
@@ -2083,13 +2106,13 @@ def _handle_get_attachments(self, param):
for attachment in jira_issue.fields.attachment:
jira_filename = "".join(attachment.filename.split())
- full_path = '{}/{}'.format(temp_vault_path, jira_filename)
+ full_path = "{}/{}".format(temp_vault_path, jira_filename)
if not self._is_safe_path(temp_vault_path, full_path):
return action_result.set_status(phantom.APP_ERROR, JIRA_ERROR_INVALID_FILE_PATH)
if extension_filter:
- file_extension = ".{}".format(jira_filename.rsplit('.')[-1])
+ file_extension = ".{}".format(jira_filename.rsplit(".")[-1])
if file_extension not in extension_list:
continue
@@ -2099,12 +2122,14 @@ def _handle_get_attachments(self, param):
ingest_file_count += 1
message = "Successfully retrieved {0} attachments of {1} total attachments from Jira ticket- {2}".format(
- ingest_file_count, len(jira_issue.fields.attachment), ticket_key)
+ ingest_file_count, len(jira_issue.fields.attachment), ticket_key
+ )
return action_result.set_status(phantom.APP_SUCCESS, message)
else:
- return action_result.set_status(phantom.APP_SUCCESS,
- "Please check the Jira Ticket ID. This issue has no attachments. Please check the provided parameters")
+ return action_result.set_status(
+ phantom.APP_SUCCESS, "Please check the Jira Ticket ID. This issue has no attachments. Please check the provided parameters"
+ )
except Exception as e:
error_message = self._get_error_message_from_exception(e)
error_text = "Unable to get attachments. {}".format(error_message)
@@ -2113,10 +2138,10 @@ def _handle_get_attachments(self, param):
def _update_container(self, issue, container_id, last_time, action_result):
update_json = {}
- update_json['data'] = issue.raw
- update_json['description'] = issue.fields.summary
+ update_json["data"] = issue.raw
+ update_json["description"] = issue.fields.summary
- url = '{0}rest/container/{1}'.format(self.get_phantom_base_url(), container_id)
+ url = "{0}rest/container/{1}".format(self.get_phantom_base_url(), container_id)
try:
r = requests.post(url, data=json.dumps(update_json), verify=False) # nosemgrep
@@ -2124,15 +2149,19 @@ def _update_container(self, issue, container_id, last_time, action_result):
except Exception as e:
error_text = self._get_error_message_from_exception(e)
self.debug_print("Error while updating the container. ", error_text)
- action_result.set_status(phantom.APP_ERROR,
- "Error occurred while updating the container for the issue key: {0}. {1}".format(issue.key, error_text))
+ action_result.set_status(
+ phantom.APP_ERROR, "Error occurred while updating the container for the issue key: {0}. {1}".format(issue.key, error_text)
+ )
return phantom.APP_ERROR
- if r.status_code != 200 or resp_json.get('failed'):
- self.debug_print("Error while updating the container. Error is: ", resp_json.get('failed'))
- action_result.set_status(phantom.APP_ERROR,
+ if r.status_code != 200 or resp_json.get("failed"):
+ self.debug_print("Error while updating the container. Error is: ", resp_json.get("failed"))
+ action_result.set_status(
+ phantom.APP_ERROR,
"Error occurred while updating the container for the issue key: {0}. Error message: {1}".format(
- issue.key, resp_json.get('failed')))
+ issue.key, resp_json.get("failed")
+ ),
+ )
return phantom.APP_ERROR
artifact_list = []
@@ -2156,8 +2185,9 @@ def _update_container(self, issue, container_id, last_time, action_result):
full_artifact = self._get_artifact_id(comment.id, container_id, full_artifact=True)
if not full_artifact:
- ret_val = self._handle_comment(comment, container_id, '{0}_{1}'.format(
- 'comment', comment.updated), artifact_list, action_result)
+ ret_val = self._handle_comment(
+ comment, container_id, "{0}_{1}".format("comment", comment.updated), artifact_list, action_result
+ )
if phantom.is_fail(ret_val):
self.debug_print("Issue key: {}. {}".format(issue.key, action_result.get_message()))
@@ -2177,8 +2207,9 @@ def _update_container(self, issue, container_id, last_time, action_result):
if comment_artifact_current_updated_time:
comment_artifact_updated_time_jira_server_tz_specific = parse(comment_artifact_current_updated_time)
- comment_artifact_updated_time_utc_tz_specific = \
- comment_artifact_updated_time_jira_server_tz_specific.astimezone(dateutil.tz.UTC)
+ comment_artifact_updated_time_utc_tz_specific = comment_artifact_updated_time_jira_server_tz_specific.astimezone(
+ dateutil.tz.UTC
+ )
# By default, we won't create the artifact for current comment
# to avoid duplicate artifacts for comments even if the fields are updated for the ticket
@@ -2189,7 +2220,8 @@ def _update_container(self, issue, container_id, last_time, action_result):
if self.is_poll_now() or not comment_artifact_current_updated_time or not create_updated_comment_artifact_not_req:
ret_val = self._handle_comment(
- comment, container_id, '{0}_{1}'.format('comment', comment.updated), artifact_list, action_result)
+ comment, container_id, "{0}_{1}".format("comment", comment.updated), artifact_list, action_result
+ )
if phantom.is_fail(ret_val):
self.debug_print("Issue key: {}. {}".format(issue.key, action_result.get_message()))
@@ -2204,7 +2236,7 @@ def _update_container(self, issue, container_id, last_time, action_result):
if artifact_json is None:
return phantom.APP_ERROR
- artifact_json['name'] = '{0}_{1}'.format('ticket fields', issue.fields.updated)
+ artifact_json["name"] = "{0}_{1}".format("ticket fields", issue.fields.updated)
artifact_list.append(artifact_json)
@@ -2233,11 +2265,11 @@ def _save_issue(self, issue, last_time, action_result):
# Build the container JSON
container_json = {}
- container_json['name'] = issue.key
- container_json['data'] = issue.raw
- container_json['description'] = issue.fields.summary
- container_json['source_data_identifier'] = issue.key
- container_json['label'] = self.get_config().get('ingest', {}).get('container_label')
+ container_json["name"] = issue.key
+ container_json["data"] = issue.raw
+ container_json["description"] = issue.fields.summary
+ container_json["source_data_identifier"] = issue.key
+ container_json["label"] = self.get_config().get("ingest", {}).get("container_label")
# Save the container
ret_val, message, container_id = self.save_container(container_json)
@@ -2262,7 +2294,7 @@ def _save_issue(self, issue, last_time, action_result):
# Check for and save comments as artifacts
try:
for comment in issue.fields.comment.comments:
- ret_val = self._handle_comment(comment, container_id, '{0}_{1}'.format('comment', comment.updated), artifact_list, action_result)
+ ret_val = self._handle_comment(comment, container_id, "{0}_{1}".format("comment", comment.updated), artifact_list, action_result)
if phantom.is_fail(ret_val):
self.debug_print("Issue key: {}. {}".format(issue.key, action_result.get_message()))
@@ -2277,7 +2309,7 @@ def _save_issue(self, issue, last_time, action_result):
if artifact_json is None:
return phantom.APP_ERROR
- artifact_json['name'] = '{0}_{1}'.format('ticket fields', issue.fields.updated)
+ artifact_json["name"] = "{0}_{1}".format("ticket fields", issue.fields.updated)
artifact_list.append(artifact_json)
@@ -2304,7 +2336,7 @@ def _on_poll(self, param): # noqa: C901
return action_result.get_status()
# Check for load_state API, use it if it is present
- if hasattr(self, 'load_state'):
+ if hasattr(self, "load_state"):
state = self.load_state()
else:
state = self._load_state()
@@ -2315,17 +2347,15 @@ def _on_poll(self, param): # noqa: C901
if not state:
self.debug_print(JIRA_ERROR_STATE_FILE_CORRUPT_ERROR)
self.save_progress(JIRA_ERROR_STATE_FILE_CORRUPT_ERROR)
- state = {
- "app_version": self.get_app_json().get('app_version')
- }
- if hasattr(self, 'save_state'):
+ state = {"app_version": self.get_app_json().get("app_version")}
+ if hasattr(self, "save_state"):
self.save_state(state)
else:
self._save_state(state)
return action_result.set_status(phantom.APP_ERROR, JIRA_ERROR_STATE_FILE_CORRUPT_ERROR)
# Get time from last poll, save now as time for this poll
- last_time = state.get('last_time', 0)
+ last_time = state.get("last_time", 0)
# This is to handle the cases if the end-user has by-mistake kept the state file status as
# {'app_version': '', 'last_time': ''} or {'app_version': '', 'last_time': null} or
@@ -2355,8 +2385,10 @@ def _on_poll(self, param): # noqa: C901
last_time_str = ts_dt_jira_ui_tzinfo.strftime(JIRA_TIME_FORMAT)
except Exception:
- return action_result.set_status(phantom.APP_ERROR,
- "Error occurred while parsing the last ingested ticket's (issue's) 'updated' timestamp from the previous ingestion run")
+ return action_result.set_status(
+ phantom.APP_ERROR,
+ "Error occurred while parsing the last ingested ticket's (issue's) 'updated' timestamp from the previous ingestion run",
+ )
# Build the query for the issue search
query = ""
@@ -2372,29 +2404,29 @@ def _on_poll(self, param): # noqa: C901
action_query = config.get(JIRA_JSON_QUERY, "")
if len(action_query) > 0:
- query = "{0}{1}{2}".format(query, ' and ' if query else '', action_query)
+ query = "{0}{1}{2}".format(query, " and " if query else "", action_query)
# If it's a poll now don't filter based on update time
if self.is_poll_now():
max_tickets = param.get(phantom.APP_JSON_CONTAINER_COUNT)
# If it's the first poll, don't filter based on update time
- elif state.get('first_run', True):
- max_tickets = int(config.get('first_run_max_tickets', DEFAULT_SCHEDULED_INTERVAL_INGESTION_COUNT))
- max_tickets = self._validate_integers(action_result, max_tickets, 'first_run_max_tickets config')
+ elif state.get("first_run", True):
+ max_tickets = int(config.get("first_run_max_tickets", DEFAULT_SCHEDULED_INTERVAL_INGESTION_COUNT))
+ max_tickets = self._validate_integers(action_result, max_tickets, "first_run_max_tickets config")
if max_tickets is None:
return action_result.get_status()
# If it's scheduled polling add a filter for update time being greater than the last poll time
else:
- max_tickets = int(config.get('max_tickets', DEFAULT_SCHEDULED_INTERVAL_INGESTION_COUNT))
- max_tickets = self._validate_integers(action_result, max_tickets, 'max_tickets config')
+ max_tickets = int(config.get("max_tickets", DEFAULT_SCHEDULED_INTERVAL_INGESTION_COUNT))
+ max_tickets = self._validate_integers(action_result, max_tickets, "max_tickets config")
if max_tickets is None:
return action_result.get_status()
- query = '{0}{1}updated>="{2}"'.format(query, ' and ' if query else '', last_time_str)
+ query = '{0}{1}updated>="{2}"'.format(query, " and " if query else "", last_time_str)
# Order by update time
- query = "{0} order by updated asc".format(query if query else '')
+ query = "{0} order by updated asc".format(query if query else "")
try:
self.debug_print("JQL Query: {0}".format(query))
@@ -2426,22 +2458,24 @@ def _on_poll(self, param): # noqa: C901
last_fetched_issue = self._jira.issue(issues[-1].key)
last_time_jira_server_tz_specific = parse(last_fetched_issue.fields.updated)
last_time_phantom_server_tz_specific = last_time_jira_server_tz_specific.astimezone(dateutil.tz.tzlocal())
- state['last_time'] = time.mktime(last_time_phantom_server_tz_specific.timetuple())
+ state["last_time"] = time.mktime(last_time_phantom_server_tz_specific.timetuple())
try:
self.debug_print("State File: {0}".format(str(state)))
self.debug_print("Last fetched Jira ticket: {0}".format(issues[-1].key))
except Exception:
- self.debug_print("Error occurred while logging the value of state file and last fetched Jira ticket, \
- continuing the on poll execution")
+ self.debug_print(
+ "Error occurred while logging the value of state file and last fetched Jira ticket, \
+ continuing the on poll execution"
+ )
# Mark the first_run as False once the scheduled or ingestion polling
# first run or every run has been successfully completed
if not self.is_poll_now():
- state['first_run'] = False
+ state["first_run"] = False
# Check for save_state API, use it if it is present
- if hasattr(self, 'save_state'):
+ if hasattr(self, "save_state"):
self.save_state(state)
else:
self._save_state(state)
@@ -2452,7 +2486,7 @@ def _on_poll(self, param): # noqa: C901
return action_result.set_status(phantom.APP_SUCCESS)
def _validate_integers(self, action_result, parameter, key, allow_zero=False):
- """ This function is a validation function to check if the provided input parameter value
+ """This function is a validation function to check if the provided input parameter value
is a non-zero positive integer and returns the integer value of the parameter itself.
:param action_result: Action result object
:param parameter: input parameter
@@ -2465,15 +2499,17 @@ def _validate_integers(self, action_result, parameter, key, allow_zero=False):
if parameter <= 0:
if allow_zero:
if parameter < 0:
- action_result.set_status(phantom.APP_ERROR, JIRA_LIMIT_VALIDATION_ALLOW_ZERO_MESSAGE .format(parameter=key))
+ action_result.set_status(phantom.APP_ERROR, JIRA_LIMIT_VALIDATION_ALLOW_ZERO_MESSAGE.format(parameter=key))
return None
else:
- action_result.set_status(phantom.APP_ERROR, JIRA_LIMIT_VALIDATION_MESSAGE .format(parameter=key))
+ action_result.set_status(phantom.APP_ERROR, JIRA_LIMIT_VALIDATION_MESSAGE.format(parameter=key))
return None
except Exception:
- error_text = \
- JIRA_LIMIT_VALIDATION_ALLOW_ZERO_MESSAGE.format(parameter=key) \
- if allow_zero else JIRA_LIMIT_VALIDATION_MESSAGE.format(parameter=key)
+ error_text = (
+ JIRA_LIMIT_VALIDATION_ALLOW_ZERO_MESSAGE.format(parameter=key)
+ if allow_zero
+ else JIRA_LIMIT_VALIDATION_MESSAGE.format(parameter=key)
+ )
action_result.set_status(phantom.APP_ERROR, error_text)
return None
@@ -2481,9 +2517,9 @@ def _validate_integers(self, action_result, parameter, key, allow_zero=False):
def handle_action(self, param):
"""Function that handles all the actions
- Args:
- Return:
- A status code
+ Args:
+ Return:
+ A status code
"""
# Get the action that we are supposed to execute for this App Run
@@ -2518,7 +2554,7 @@ def handle_action(self, param):
return action_execution_status
-if __name__ == '__main__':
+if __name__ == "__main__":
import argparse
@@ -2528,10 +2564,10 @@ def handle_action(self, param):
argparser = argparse.ArgumentParser()
- argparser.add_argument('input_test_json', help='Input Test JSON file')
- argparser.add_argument('-u', '--username', help='username', required=False)
- argparser.add_argument('-p', '--password', help='password', required=False)
- argparser.add_argument('-v', '--verify', action='store_true', help='verify', required=False, default=False)
+ argparser.add_argument("input_test_json", help="Input Test JSON file")
+ argparser.add_argument("-u", "--username", help="username", required=False)
+ argparser.add_argument("-p", "--password", help="password", required=False)
+ argparser.add_argument("-v", "--verify", action="store_true", help="verify", required=False, default=False)
args = argparser.parse_args()
session_id = None
@@ -2544,27 +2580,29 @@ def handle_action(self, param):
# User specified a username but not a password, so ask
import getpass
+
password = getpass.getpass("Password: ")
if username and password:
try:
print("Accessing the Login page")
r = requests.get(phantom.BaseConnector._get_phantom_base_url() + "login", verify=verify, timeout=JIRA_DEFAULT_TIMEOUT)
- csrftoken = r.cookies['csrftoken']
+ csrftoken = r.cookies["csrftoken"]
data = dict()
- data['username'] = username
- data['password'] = password
- data['csrfmiddlewaretoken'] = csrftoken
+ data["username"] = username
+ data["password"] = password
+ data["csrfmiddlewaretoken"] = csrftoken
headers = dict()
- headers['Cookie'] = 'csrftoken=' + csrftoken
- headers['Referer'] = phantom.BaseConnector._get_phantom_base_url() + 'login'
+ headers["Cookie"] = "csrftoken=" + csrftoken
+ headers["Referer"] = phantom.BaseConnector._get_phantom_base_url() + "login"
print("Logging into Platform to get the session id")
- r2 = requests.post(phantom.BaseConnector._get_phantom_base_url() + "login", verify=verify, data=data, headers=headers,
- timeout=JIRA_DEFAULT_TIMEOUT)
- session_id = r2.cookies['sessionid']
+ r2 = requests.post(
+ phantom.BaseConnector._get_phantom_base_url() + "login", verify=verify, data=data, headers=headers, timeout=JIRA_DEFAULT_TIMEOUT
+ )
+ session_id = r2.cookies["sessionid"]
except Exception as e:
print("Unable to get session id from the platform. Error: " + str(e))
sys.exit(1)
@@ -2578,8 +2616,8 @@ def handle_action(self, param):
connector.print_progress_message = True
if session_id is not None:
- in_json['user_session_token'] = session_id
- connector._set_csrf_info(csrftoken, headers['Referer'])
+ in_json["user_session_token"] = session_id
+ connector._set_csrf_info(csrftoken, headers["Referer"])
ret_val = connector._handle_action(json.dumps(in_json), None)
print(json.dumps(json.loads(ret_val), indent=4))
diff --git a/jira_consts.py b/jira_consts.py
index d897f44..daf983d 100644
--- a/jira_consts.py
+++ b/jira_consts.py
@@ -51,28 +51,34 @@
JIRA_JSON_TO_ID = "to_id"
JIRA_JSON_LINK_TYPE = "link_type"
JIRA_JSON_UPDATED_AT = "updated_at"
-JIRA_JSON_CONTAINER = 'container'
-JIRA_JSON_SDI = 'source_data_identifier'
-JIRA_JSON_LABEL = 'label'
-JIRA_JSON_CEF = 'cef'
-JIRA_JSON_UNRESOLVED = 'Unresolved'
-JIRA_JSON_CUSTOM_FIELDS = 'custom_fields'
-JIRA_JSON_USERNAME = 'username'
-JIRA_JSON_DISPLAY_NAME = 'display_name'
-JIRA_JSON_USER_ACCOUNT_ID = 'user_account_id'
-JIRA_JSON_TIMESPENT = 'time_spent'
+JIRA_JSON_CONTAINER = "container"
+JIRA_JSON_SDI = "source_data_identifier"
+JIRA_JSON_LABEL = "label"
+JIRA_JSON_CEF = "cef"
+JIRA_JSON_UNRESOLVED = "Unresolved"
+JIRA_JSON_CUSTOM_FIELDS = "custom_fields"
+JIRA_JSON_USERNAME = "username"
+JIRA_JSON_DISPLAY_NAME = "display_name"
+JIRA_JSON_USER_ACCOUNT_ID = "user_account_id"
+JIRA_JSON_TIMESPENT = "time_spent"
JIRA_RESPONSE_ERROR_MESSAGES_KEY = "errorMessages"
JIRA_RESPONSE_ERRORS_KEY = "errors"
-JIRA_WATCHERS_ERROR = "Please provide either 'user_account_id' or 'username' action parameter. " \
+JIRA_WATCHERS_ERROR = (
+ "Please provide either 'user_account_id' or 'username' action parameter. "
"For JIRA on-prem, use 'username' action parameter, and, for JIRA cloud, use 'user_account_id' action parameter"
-JIRA_SEARCH_USERS_ERROR = "Please provide either 'display_name' or 'username' action parameter. " \
+)
+JIRA_SEARCH_USERS_ERROR = (
+ "Please provide either 'display_name' or 'username' action parameter. "
"For JIRA on-prem, use 'username' action parameter, and, for JIRA cloud, use 'display_name' action parameter"
+)
JIRA_CUSTOM_FIELD_FORMAT_ERROR = "Could not load JSON formatted list from the custom_fields asset configuration parameter. {0}"
JIRA_CUSTOM_FIELD_NON_EMPTY_ERROR = "Please provide 'custom_fields' asset configuration parameter as a non-empty JSON formatted list"
-JIRA_ASSIGNEE_ERROR = "Please provide either 'assignee' or 'assignee_account_id' action parameter. " \
+JIRA_ASSIGNEE_ERROR = (
+ "Please provide either 'assignee' or 'assignee_account_id' action parameter. "
"For JIRA on-prem, use 'assignee' action parameter, and, for JIRA cloud, use 'assignee_account_id' action parameter"
+)
JIRA_INVALID_LIMIT = "Please provide non-zero positive integer in limit"
JIRA_ERROR_STATE_FILE_CORRUPT_ERROR = "Error occurred while loading the state file due to its unexpected format.\
Resetting the state file with the default format. Please try again."
@@ -86,9 +92,11 @@
JIRA_ERROR_TICKET_ASSIGNMENT_FAILED = "Ticket assignment to user '{0}' failed. {1}"
JIRA_ERROR_CREATE_TICKET_FAILED = "Ticket creation failed"
JIRA_SUCCESS_TICKET_CREATED = "Created ticket with id: {id}, key: {key}"
-JIRA_ERROR_ARTIFACT_NOT_FOUND_IN_CONTAINER = "Either the ticket artifact with issue key: {issue_key} got deleted " \
- "from the container: {container_id} or the type of the issue has changed on the JIRA instance." \
+JIRA_ERROR_ARTIFACT_NOT_FOUND_IN_CONTAINER = (
+ "Either the ticket artifact with issue key: {issue_key} got deleted "
+ "from the container: {container_id} or the type of the issue has changed on the JIRA instance."
"Please delete the container and re-run the ingestion."
+)
JIRA_ERROR_FILE_NOT_IN_VAULT = "Could not find specified vault ID in vault"
JIRA_ERROR_ATTACH_FAILED = "Adding attachment failed. {0}"
JIRA_ERROR_LIST_TICKETS_FAILED = "Failed to get ticket listing"
@@ -98,13 +106,17 @@
JIRA_ERROR_ISSUE_VALID_RESOLUTION = "Input resolution does not seem to be valid"
JIRA_ERROR_UPDATE_NO_PARAM = "Either the Vault ID or the JSON field must be filled out to perform this action"
JIRA_ERROR_UPDATE_FAILED = "Unable to update the ticket with the given JSON"
-JIRA_ERROR_COMMENT_SET_STATUS_FAILED = "Comment could not be added successfully due to either permissions or configuration issue " \
+JIRA_ERROR_COMMENT_SET_STATUS_FAILED = (
+ "Comment could not be added successfully due to either permissions or configuration issue "
"(changing the status of the ticket to Closed and then, trying to add comment to it is one such scenario)."
+)
JIRA_SUCCESS_TICKET_UPDATED = "Successfully updated the ticket"
JIRA_SUCCESS_TICKET_DELETED = "Successfully deleted the ticket"
-JIRA_ERROR_INPUT_FIELDS_NOT_THE_ONLY_ONE = "Invalid fields value." \
- " The input json has a 'fields' key in it in addition to other keys." \
+JIRA_ERROR_INPUT_FIELDS_NOT_THE_ONLY_ONE = (
+ "Invalid fields value."
+ " The input json has a 'fields' key in it in addition to other keys."
" Either specify a dictionary with only one parent 'fields' key or multiple keys without the 'fields' key"
+)
JIRA_ERROR_FAILED = "Some tickets had issues during ingestion, see logs for the details"
JIRA_ERROR_NEGATIVE_INPUT = "'start_index' cannot be a negative value"
JIRA_LIMIT_VALIDATION_ALLOW_ZERO_MESSAGE = "Please provide zero or positive integer value in the {parameter} parameter"
diff --git a/release_notes/3.7.2.md b/release_notes/3.7.2.md
new file mode 100644
index 0000000..3a192ed
--- /dev/null
+++ b/release_notes/3.7.2.md
@@ -0,0 +1,2 @@
+* Removed cryptography dependencies in order to use platform packages [PAPP-31088, PAPP-33451]
+* Updated min_phantom_version and python packages for security [PSAAS-20418]
\ No newline at end of file
diff --git a/tox.ini b/tox.ini
index c4644ad..720a141 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,7 +1,4 @@
[flake8]
max-line-length = 145
max-complexity = 28
-extend-ignore = F403,E128,E126,E111,E121,E127,E731,E201,E202,F405,E722,D,W292
-
-[isort]
-line_length = 145
+extend-ignore = F403,E128,E126,E121,E127,E731,E201,E202,E203,E701,F405,E722,D,W503
diff --git a/wheels/py3/beautifulsoup4-4.9.1-py3-none-any.whl b/wheels/py3/beautifulsoup4-4.9.1-py3-none-any.whl
deleted file mode 100644
index 080b2f8..0000000
Binary files a/wheels/py3/beautifulsoup4-4.9.1-py3-none-any.whl and /dev/null differ
diff --git a/wheels/py3/packaging-24.2-py3-none-any.whl b/wheels/py3/packaging-24.2-py3-none-any.whl
new file mode 100644
index 0000000..b38a4a5
Binary files /dev/null and b/wheels/py3/packaging-24.2-py3-none-any.whl differ
diff --git a/wheels/py3/soupsieve-2.5-py3-none-any.whl b/wheels/py3/soupsieve-2.5-py3-none-any.whl
deleted file mode 100644
index e1be128..0000000
Binary files a/wheels/py3/soupsieve-2.5-py3-none-any.whl and /dev/null differ
diff --git a/wheels/py3/typing_extensions-4.12.2-py3-none-any.whl b/wheels/py3/typing_extensions-4.12.2-py3-none-any.whl
new file mode 100644
index 0000000..f6cc799
Binary files /dev/null and b/wheels/py3/typing_extensions-4.12.2-py3-none-any.whl differ
diff --git a/wheels/py36/cryptography-40.0.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl b/wheels/py36/cryptography-40.0.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
deleted file mode 100644
index 3aec077..0000000
Binary files a/wheels/py36/cryptography-40.0.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl and /dev/null differ
diff --git a/wheels/shared/pycparser-2.21-py2.py3-none-any.whl b/wheels/shared/pycparser-2.21-py2.py3-none-any.whl
deleted file mode 100644
index fef6735..0000000
Binary files a/wheels/shared/pycparser-2.21-py2.py3-none-any.whl and /dev/null differ
diff --git a/wheels/shared/python_dateutil-2.8.1-py2.py3-none-any.whl b/wheels/shared/python_dateutil-2.8.1-py2.py3-none-any.whl
deleted file mode 100644
index 4dc1855..0000000
Binary files a/wheels/shared/python_dateutil-2.8.1-py2.py3-none-any.whl and /dev/null differ
diff --git a/wheels/shared/six-1.16.0-py2.py3-none-any.whl b/wheels/shared/six-1.16.0-py2.py3-none-any.whl
deleted file mode 100644
index fd94265..0000000
Binary files a/wheels/shared/six-1.16.0-py2.py3-none-any.whl and /dev/null differ