diff --git a/dojo/jira_link/helper.py b/dojo/jira_link/helper.py index 83e9f0c28f..f13dc03134 100644 --- a/dojo/jira_link/helper.py +++ b/dojo/jira_link/helper.py @@ -1251,7 +1251,9 @@ def close_epic(eng, push_to_jira, **kwargs): r = requests.post( url=req_url, auth=HTTPBasicAuth(jira_instance.username, jira_instance.password), - json=json_data) + json=json_data, + timeout=settings.REQUESTS_TIMEOUT, + ) if r.status_code != 204: logger.warning(f"JIRA close epic failed with error: {r.text}") return False diff --git a/dojo/management/commands/import_github_languages.py b/dojo/management/commands/import_github_languages.py index 09e1be8bd2..9e1c45ffb3 100644 --- a/dojo/management/commands/import_github_languages.py +++ b/dojo/management/commands/import_github_languages.py @@ -2,6 +2,7 @@ import logging import requests +from django.conf import settings from django.core.management.base import BaseCommand from dojo.models import Language_Type @@ -22,7 +23,12 @@ def handle(self, *args, **options): logger.info("Started importing languages from GitHub ...") try: - deserialized = json.loads(requests.get("https://raw.githubusercontent.com/ozh/github-colors/master/colors.json").text) + deserialized = json.loads( + requests.get( + "https://raw.githubusercontent.com/ozh/github-colors/master/colors.json", + timeout=settings.REQUESTS_TIMEOUT, + ).text, + ) except: msg = "Invalid format" raise Exception(msg) diff --git a/dojo/notifications/helper.py b/dojo/notifications/helper.py index 46d0339dd3..b831ef4ed7 100644 --- a/dojo/notifications/helper.py +++ b/dojo/notifications/helper.py @@ -227,7 +227,9 @@ def _post_slack_message(channel): "channel": channel, "username": get_system_setting("slack_username"), "text": create_notification_message(event, user, "slack", *args, **kwargs), - }) + }, + timeout=settings.REQUESTS_TIMEOUT, + ) if "error" in res.text: logger.error("Slack is complaining. See raw text below.") @@ -284,7 +286,9 @@ def send_msteams_notification(event, user=None, *args, **kwargs): res = requests.request( method="POST", url=get_system_setting("msteams_url"), - data=create_notification_message(event, None, "msteams", *args, **kwargs)) + data=create_notification_message(event, None, "msteams", *args, **kwargs), + timeout=settings.REQUESTS_TIMEOUT, + ) if res.status_code != 200: logger.error("Error when sending message to Microsoft Teams") logger.error(res.status_code) @@ -518,7 +522,9 @@ def get_slack_user_id(user_email): res = requests.request( method="POST", url="https://slack.com/api/users.lookupByEmail", - data={"token": get_system_setting("slack_token"), "email": user_email}) + data={"token": get_system_setting("slack_token"), "email": user_email}, + timeout=settings.REQUESTS_TIMEOUT, + ) user = json.loads(res.text) diff --git a/dojo/pipeline.py b/dojo/pipeline.py index ee2dc0ae18..2aa17ebd88 100644 --- a/dojo/pipeline.py +++ b/dojo/pipeline.py @@ -81,7 +81,11 @@ def update_azure_groups(backend, uid, user=None, social=None, *args, **kwargs): request_headers = {"Authorization": "Bearer " + token} if is_group_id(group_from_response): logger.debug("detected " + group_from_response + " as groupID and will fetch the displayName from microsoft graph") - group_name_request = requests.get((str(soc.extra_data["resource"]) + "/v1.0/groups/" + str(group_from_response) + "?$select=displayName"), headers=request_headers) + group_name_request = requests.get( + (str(soc.extra_data["resource"]) + "/v1.0/groups/" + str(group_from_response) + "?$select=displayName"), + headers=request_headers, + timeout=settings.REQUESTS_TIMEOUT, + ) group_name_request.raise_for_status() group_name_request_json = group_name_request.json() group_name = group_name_request_json["displayName"] diff --git a/dojo/settings/.settings.dist.py.sha256sum b/dojo/settings/.settings.dist.py.sha256sum index 59acc056a4..2fbe294ebd 100644 --- a/dojo/settings/.settings.dist.py.sha256sum +++ b/dojo/settings/.settings.dist.py.sha256sum @@ -1 +1 @@ -58e2f6cb0ed2c041fe2741d955b72cb7540bfb0923f489d6324717fcf00039da +a248299930cd71eb02f4526ed11a02f4d0f1937d1e485b07ec01948241965903 diff --git a/dojo/settings/settings.dist.py b/dojo/settings/settings.dist.py index 2571d99b0c..6699971144 100644 --- a/dojo/settings/settings.dist.py +++ b/dojo/settings/settings.dist.py @@ -306,6 +306,9 @@ DD_NOTIFICATIONS_SYSTEM_LEVEL_TRUMP=(list, ["user_mentioned", "review_requested"]), # When enabled, force the password field to be required for creating/updating users DD_REQUIRE_PASSWORD_ON_USER=(bool, True), + # For HTTP requests, how long connection is open before timeout + # This settings apply only on requests performed by "requests" lib used in Dojo code (if some included lib is using "requests" as well, this does not apply there) + DD_REQUESTS_TIMEOUT=(int, 30), ) @@ -1771,6 +1774,11 @@ def saml2_attrib_map_format(dict): # ------------------------------------------------------------------------------ NOTIFICATIONS_SYSTEM_LEVEL_TRUMP = env("DD_NOTIFICATIONS_SYSTEM_LEVEL_TRUMP") +# ------------------------------------------------------------------------------ +# Timeouts +# ------------------------------------------------------------------------------ +REQUESTS_TIMEOUT = env("DD_REQUESTS_TIMEOUT") + # ------------------------------------------------------------------------------ # Ignored Warnings # ------------------------------------------------------------------------------ diff --git a/dojo/tools/api_bugcrowd/api_client.py b/dojo/tools/api_bugcrowd/api_client.py index f180d64325..7e9ac2b91c 100644 --- a/dojo/tools/api_bugcrowd/api_client.py +++ b/dojo/tools/api_bugcrowd/api_client.py @@ -1,6 +1,7 @@ from urllib.parse import urlencode import requests +from django.conf import settings class BugcrowdAPI: @@ -52,7 +53,10 @@ def get_findings(self, program, target): next = f"{self.bugcrowd_api_url}/submissions?{params_encoded}" while next != "": - response = self.session.get(url=next) + response = self.session.get( + url=next, + timeout=settings.REQUESTS_TIMEOUT, + ) response.raise_for_status() if response.ok: data = response.json() @@ -75,12 +79,14 @@ def test_connection(self): # Request programs response_programs = self.session.get( url=f"{self.bugcrowd_api_url}/programs", + timeout=settings.REQUESTS_TIMEOUT, ) response_programs.raise_for_status() # Request submissions to validate the org token response_subs = self.session.get( url=f"{self.bugcrowd_api_url}/submissions", + timeout=settings.REQUESTS_TIMEOUT, ) response_subs.raise_for_status() if response_programs.ok and response_subs.ok: @@ -95,6 +101,7 @@ def test_connection(self): # Request targets to validate the org token response_targets = self.session.get( url=f"{self.bugcrowd_api_url}/targets", + timeout=settings.REQUESTS_TIMEOUT, ) response_targets.raise_for_status() if response_targets.ok: diff --git a/dojo/tools/api_cobalt/api_client.py b/dojo/tools/api_cobalt/api_client.py index 21318143e2..acd01635e9 100644 --- a/dojo/tools/api_cobalt/api_client.py +++ b/dojo/tools/api_cobalt/api_client.py @@ -1,4 +1,5 @@ import requests +from django.conf import settings class CobaltAPI: @@ -36,6 +37,7 @@ def get_assets(self): response = self.session.get( url=f"{self.cobalt_api_url}/assets?limit=1000", headers=self.get_headers(), + timeout=settings.REQUESTS_TIMEOUT, ) if response.ok: @@ -56,6 +58,7 @@ def get_findings(self, asset_id): response = self.session.get( url=f"{self.cobalt_api_url}/findings?limit=1000&asset={asset_id}", headers=self.get_headers(), + timeout=settings.REQUESTS_TIMEOUT, ) if response.ok: @@ -72,12 +75,14 @@ def test_connection(self): response_orgs = self.session.get( url=f"{self.cobalt_api_url}/orgs", headers=self.get_headers(), + timeout=settings.REQUESTS_TIMEOUT, ) # Request assets to validate the org token response_assets = self.session.get( url=f"{self.cobalt_api_url}/assets", headers=self.get_headers(), + timeout=settings.REQUESTS_TIMEOUT, ) if response_orgs.ok and response_assets.ok: diff --git a/dojo/tools/api_edgescan/api_client.py b/dojo/tools/api_edgescan/api_client.py index df0de92a47..580d753226 100644 --- a/dojo/tools/api_edgescan/api_client.py +++ b/dojo/tools/api_edgescan/api_client.py @@ -2,6 +2,7 @@ from json.decoder import JSONDecodeError import requests +from django.conf import settings class EdgescanAPI: @@ -42,6 +43,7 @@ def get_findings(self, asset_ids): url=url, headers=self.get_headers(), proxies=self.get_proxies(), + timeout=settings.REQUESTS_TIMEOUT, ) response.raise_for_status() return response.json() diff --git a/dojo/tools/api_sonarqube/api_client.py b/dojo/tools/api_sonarqube/api_client.py index 9dd512efe9..9c04ceea64 100644 --- a/dojo/tools/api_sonarqube/api_client.py +++ b/dojo/tools/api_sonarqube/api_client.py @@ -1,4 +1,5 @@ import requests +from django.conf import settings from requests.exceptions import JSONDecodeError as RequestsJSONDecodeError from dojo.utils import prepare_for_view @@ -75,6 +76,7 @@ def find_project(self, project_name, organization=None, branch=None): url=f"{self.sonar_api_url}/components/search", params=parameters, headers=self.default_headers, + timeout=settings.REQUESTS_TIMEOUT, ) if not response.ok: @@ -120,6 +122,7 @@ def get_project(self, project_key, organization=None, branch=None): url=f"{self.sonar_api_url}/components/show", params=parameters, headers=self.default_headers, + timeout=settings.REQUESTS_TIMEOUT, ) if not response.ok: @@ -173,6 +176,7 @@ def find_issues( url=f"{self.sonar_api_url}/issues/search", params=request_filter, headers=self.default_headers, + timeout=settings.REQUESTS_TIMEOUT, ) if not response.ok: @@ -215,6 +219,7 @@ def find_hotspots(self, project_key, organization=None, branch=None): url=f"{self.sonar_api_url}/hotspots/search", params=request_filter, headers=self.default_headers, + timeout=settings.REQUESTS_TIMEOUT, ) if not response.ok: @@ -250,6 +255,7 @@ def get_issue(self, issue_key): url=f"{self.sonar_api_url}/issues/search", params=request_filter, headers=self.default_headers, + timeout=settings.REQUESTS_TIMEOUT, ) if not response.ok: @@ -290,6 +296,7 @@ def get_rule(self, rule_id, organization=None): url=f"{self.sonar_api_url}/rules/show", params=request_filter, headers=self.default_headers, + timeout=settings.REQUESTS_TIMEOUT, ) if not response.ok: msg = ( @@ -314,6 +321,7 @@ def get_hotspot_rule(self, rule_id): url=f"{self.sonar_api_url}/hotspots/show", params={"hotspot": rule_id}, headers=self.default_headers, + timeout=settings.REQUESTS_TIMEOUT, ) if not response.ok: msg = ( @@ -357,6 +365,7 @@ def transition_issue(self, issue_key, transition): url=f"{self.sonar_api_url}/issues/do_transition", data={"issue": issue_key, "transition": transition}, headers=self.default_headers, + timeout=settings.REQUESTS_TIMEOUT, ) if not response.ok: @@ -378,6 +387,7 @@ def add_comment(self, issue_key, text): url=f"{self.sonar_api_url}/issues/add_comment", data={"issue": issue_key, "text": text}, headers=self.default_headers, + timeout=settings.REQUESTS_TIMEOUT, ) if not response.ok: msg = ( @@ -397,6 +407,7 @@ def test_connection(self): url=f"{self.sonar_api_url}/components/search", params=parameters, headers=self.default_headers, + timeout=settings.REQUESTS_TIMEOUT, ) if not response.ok: diff --git a/dojo/tools/risk_recon/api.py b/dojo/tools/risk_recon/api.py index 898db341ec..ec505e15bd 100644 --- a/dojo/tools/risk_recon/api.py +++ b/dojo/tools/risk_recon/api.py @@ -1,4 +1,5 @@ import requests +from django.conf import settings class RiskReconAPI: @@ -33,6 +34,7 @@ def map_toes(self): response = self.session.get( url=f"{self.url}/toes", headers={"accept": "application/json", "Authorization": self.key}, + timeout=settings.REQUESTS_TIMEOUT, ) if response.ok: @@ -75,6 +77,7 @@ def get_findings(self): "accept": "application/json", "Authorization": self.key, }, + timeout=settings.REQUESTS_TIMEOUT, ) if response.ok: diff --git a/ruff.toml b/ruff.toml index a65b027a23..6773131a25 100644 --- a/ruff.toml +++ b/ruff.toml @@ -41,7 +41,7 @@ select = [ "UP", "YTT", "ASYNC", - "S2", "S5", "S7", "S101", "S104", "S105", "S106", "S108", "S112", "S311", + "S2", "S5", "S7", "S101", "S104", "S105", "S106", "S108", "S311", "S112", "S113", "FBT001", "FBT003", "A003", "A004", "A006", "COM",