mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-08-19 09:30:21 +00:00
feat(jira): request timeout (#11602)
This commit is contained in:
@@ -14,6 +14,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
|
||||
- `identity_storage_service_level_admins_scoped` check for OCI provider CIS 3.1 control 1.15, ensuring storage service-level administrators exclude delete permissions [(#11523)](https://github.com/prowler-cloud/prowler/pull/11523)
|
||||
- `cosmosdb_account_automatic_failover_enabled` check for Azure provider [(#11031)](https://github.com/prowler-cloud/prowler/pull/11031)
|
||||
- `cosmosdb_account_backup_policy_continuous` check for Azure provider [(#11032)](https://github.com/prowler-cloud/prowler/pull/11032)
|
||||
- Jira timeout preventing the calls from hanging indefinitely when the Jira endpoint is unreachable or slow [(#11602)](https://github.com/prowler-cloud/prowler/pull/11602)
|
||||
- TLS certificate verification in the `codepipeline_project_repo_private` check, which previously used an unverified SSL context, leaving the repository-visibility probe open to MITM tampering [(#11603)](https://github.com/prowler-cloud/prowler/pull/11603)
|
||||
|
||||
### 🔄 Changed
|
||||
|
||||
@@ -341,6 +341,7 @@ class Jira:
|
||||
}
|
||||
TOKEN_URL = "https://auth.atlassian.com/oauth/token"
|
||||
API_TOKEN_URL = "https://api.atlassian.com/oauth/token/accessible-resources"
|
||||
REQUEST_TIMEOUT = 30
|
||||
HEADER_TEMPLATE = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Force-Accept-Language": "true",
|
||||
@@ -578,7 +579,12 @@ class Jira:
|
||||
}
|
||||
|
||||
headers = self.get_headers(content_type_json=True)
|
||||
response = requests.post(self.TOKEN_URL, json=body, headers=headers)
|
||||
response = requests.post(
|
||||
self.TOKEN_URL,
|
||||
json=body,
|
||||
headers=headers,
|
||||
timeout=self.REQUEST_TIMEOUT,
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
tokens = response.json()
|
||||
@@ -630,12 +636,17 @@ class Jira:
|
||||
response = requests.get(
|
||||
f"https://{domain}.atlassian.net/_edge/tenant_info",
|
||||
headers=headers,
|
||||
timeout=self.REQUEST_TIMEOUT,
|
||||
)
|
||||
response = response.json()
|
||||
return response.get("cloudId")
|
||||
else:
|
||||
headers = self.get_headers(access_token)
|
||||
response = requests.get(self.API_TOKEN_URL, headers=headers)
|
||||
response = requests.get(
|
||||
self.API_TOKEN_URL,
|
||||
headers=headers,
|
||||
timeout=self.REQUEST_TIMEOUT,
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
resources = response.json()
|
||||
@@ -717,7 +728,12 @@ class Jira:
|
||||
}
|
||||
|
||||
headers = self.get_headers(content_type_json=True)
|
||||
response = requests.post(url, json=body, headers=headers)
|
||||
response = requests.post(
|
||||
url,
|
||||
json=body,
|
||||
headers=headers,
|
||||
timeout=self.REQUEST_TIMEOUT,
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
tokens = response.json()
|
||||
@@ -874,6 +890,7 @@ class Jira:
|
||||
response = requests.get(
|
||||
f"https://api.atlassian.com/ex/jira/{self.cloud_id}/rest/api/3/project",
|
||||
headers=headers,
|
||||
timeout=self.REQUEST_TIMEOUT,
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
@@ -941,6 +958,7 @@ class Jira:
|
||||
response = requests.get(
|
||||
f"https://api.atlassian.com/ex/jira/{self.cloud_id}/rest/api/3/issue/createmeta?projectKeys={project_key}&expand=projects.issuetypes.fields",
|
||||
headers=headers,
|
||||
timeout=self.REQUEST_TIMEOUT,
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
@@ -986,6 +1004,7 @@ class Jira:
|
||||
response = requests.get(
|
||||
f"https://api.atlassian.com/ex/jira/{self.cloud_id}/rest/api/3/project",
|
||||
headers=headers,
|
||||
timeout=self.REQUEST_TIMEOUT,
|
||||
)
|
||||
if response.status_code == 200:
|
||||
projects_data = {}
|
||||
@@ -1001,6 +1020,7 @@ class Jira:
|
||||
project_response = requests.get(
|
||||
f"https://api.atlassian.com/ex/jira/{self.cloud_id}/rest/api/3/issue/createmeta?projectKeys={project['key']}&expand=projects.issuetypes.fields",
|
||||
headers=headers,
|
||||
timeout=self.REQUEST_TIMEOUT,
|
||||
)
|
||||
if project_response.status_code == 200:
|
||||
project_metadata = project_response.json()
|
||||
@@ -1923,6 +1943,7 @@ class Jira:
|
||||
f"https://api.atlassian.com/ex/jira/{self.cloud_id}/rest/api/3/issue",
|
||||
json=payload,
|
||||
headers=headers,
|
||||
timeout=self.REQUEST_TIMEOUT,
|
||||
)
|
||||
|
||||
if response.status_code != 201:
|
||||
@@ -2127,6 +2148,7 @@ class Jira:
|
||||
f"https://api.atlassian.com/ex/jira/{self.cloud_id}/rest/api/3/issue",
|
||||
json=payload,
|
||||
headers=headers,
|
||||
timeout=self.REQUEST_TIMEOUT,
|
||||
)
|
||||
|
||||
if response.status_code != 201:
|
||||
|
||||
@@ -339,6 +339,88 @@ class TestJiraIntegration:
|
||||
with pytest.raises(JiraRefreshTokenError):
|
||||
self.jira_integration.refresh_access_token()
|
||||
|
||||
@patch("prowler.lib.outputs.jira.jira.requests.post")
|
||||
@patch.object(Jira, "get_cloud_id", return_value="test_cloud_id")
|
||||
def test_get_auth_sends_timeout(self, mock_get_cloud_id, mock_post):
|
||||
"""get_auth must pass a request timeout to avoid hanging on an unresponsive Jira."""
|
||||
# To disable vulture
|
||||
mock_get_cloud_id = mock_get_cloud_id
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {
|
||||
"access_token": "test_access_token",
|
||||
"refresh_token": "test_refresh_token",
|
||||
"expires_in": 3600,
|
||||
}
|
||||
mock_post.return_value = mock_response
|
||||
|
||||
self.jira_integration.get_auth("test_auth_code")
|
||||
|
||||
assert mock_post.call_args.kwargs["timeout"] == Jira.REQUEST_TIMEOUT
|
||||
|
||||
@patch("prowler.lib.outputs.jira.jira.requests.get")
|
||||
def test_get_cloud_id_sends_timeout(self, mock_get):
|
||||
"""get_cloud_id (OAuth path) must pass a request timeout."""
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = [{"id": "test_cloud_id"}]
|
||||
mock_get.return_value = mock_response
|
||||
|
||||
self.jira_integration.get_cloud_id("test_access_token")
|
||||
|
||||
assert mock_get.call_args.kwargs["timeout"] == Jira.REQUEST_TIMEOUT
|
||||
|
||||
@patch("prowler.lib.outputs.jira.jira.requests.get")
|
||||
def test_get_cloud_id_basic_auth_sends_timeout(self, mock_get):
|
||||
"""get_cloud_id (basic-auth tenant_info path) must pass a request timeout."""
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {"cloudId": "test_cloud_id"}
|
||||
mock_get.return_value = mock_response
|
||||
|
||||
self.jira_integration_basic_auth.get_cloud_id(domain=self.domain)
|
||||
|
||||
assert mock_get.call_args.kwargs["timeout"] == Jira.REQUEST_TIMEOUT
|
||||
|
||||
@patch("prowler.lib.outputs.jira.jira.requests.post")
|
||||
def test_refresh_access_token_sends_timeout(self, mock_post):
|
||||
"""refresh_access_token must pass a request timeout."""
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {
|
||||
"access_token": "new_access_token",
|
||||
"refresh_token": "new_refresh_token",
|
||||
"expires_in": 3600,
|
||||
}
|
||||
mock_post.return_value = mock_response
|
||||
|
||||
self.jira_integration.refresh_access_token()
|
||||
|
||||
assert mock_post.call_args.kwargs["timeout"] == Jira.REQUEST_TIMEOUT
|
||||
|
||||
@patch.object(Jira, "get_access_token", return_value="valid_access_token")
|
||||
@patch.object(
|
||||
Jira, "cloud_id", new_callable=PropertyMock, return_value="test_cloud_id"
|
||||
)
|
||||
@patch("prowler.lib.outputs.jira.jira.requests.get")
|
||||
def test_get_projects_sends_timeout(
|
||||
self, mock_get, mock_cloud_id, mock_get_access_token
|
||||
):
|
||||
"""get_projects must pass a request timeout."""
|
||||
# To disable vulture
|
||||
mock_cloud_id = mock_cloud_id
|
||||
mock_get_access_token = mock_get_access_token
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = [{"key": "PROJ1", "name": "Project One"}]
|
||||
mock_get.return_value = mock_response
|
||||
|
||||
self.jira_integration.get_projects()
|
||||
|
||||
assert mock_get.call_args.kwargs["timeout"] == Jira.REQUEST_TIMEOUT
|
||||
|
||||
@patch.object(Jira, "get_auth", return_value=None)
|
||||
@patch.object(
|
||||
Jira,
|
||||
|
||||
Reference in New Issue
Block a user