diff --git a/mailer.py b/mailer.py index dd3d55532..c2b931210 100644 --- a/mailer.py +++ b/mailer.py @@ -42,7 +42,8 @@ def send_simple_message(self, data: Dict) -> Response: """ data['from'] = self.sender try: - return requests.post(f"{self.api_url}/messages", auth=self.auth, data=data) + return requests.post(f"{self.api_url}/messages", auth=self.auth, data=data, + timeout=(3.05, 10)) except (requests.HTTPError, requests.ConnectionError): traceback.print_exc() raise FailedToSendMail diff --git a/mod_auth/controllers.py b/mod_auth/controllers.py index a1f773774..d9c8c28b6 100755 --- a/mod_auth/controllers.py +++ b/mod_auth/controllers.py @@ -20,6 +20,7 @@ DeactivationForm, LoginForm, ResetForm, RoleChangeForm, SignupForm) from mod_auth.models import Role, User +from utility import HTTP_TIMEOUT mod_auth = Blueprint('auth', __name__) @@ -139,7 +140,7 @@ def github_token_validity(token: str): url = f'https://api.github.com/applications/{github_client_id}/token' session = requests.Session() session.auth = (github_client_id, github_client_secret) - response = session.post(url, json={"access_token": token}) + response = session.post(url, json={"access_token": token}, timeout=HTTP_TIMEOUT) return response.status_code == 200 @@ -182,7 +183,7 @@ def fetch_username_from_token(user=None) -> Any: session = requests.Session() session.auth = (user.email, user.github_token) try: - response = session.get(url, timeout=(3.05, 10)) + response = session.get(url, timeout=HTTP_TIMEOUT) data = response.json() return data.get('login') except Exception as e: @@ -209,7 +210,7 @@ def github_callback(): 'code': request.args['code'] } headers = {'Accept': 'application/json'} - r = requests.post(url, params=payload, headers=headers) + r = requests.post(url, params=payload, headers=headers, timeout=HTTP_TIMEOUT) response = r.json() if 'access_token' in response: diff --git a/mod_upload/controllers.py b/mod_upload/controllers.py index 1e38d895b..dba1a33b3 100755 --- a/mod_upload/controllers.py +++ b/mod_upload/controllers.py @@ -27,6 +27,7 @@ UploadForm) from mod_upload.models import (FTPCredentials, Platform, QueuedSample, Upload, UploadLog) +from utility import HTTP_TIMEOUT mod_upload = Blueprint('upload', __name__) @@ -103,7 +104,7 @@ def make_github_issue(title, body=None, labels=None) -> Any: issue = {'title': title, 'body': body, 'labels': labels} - r = session.post(url, json.dumps(issue)) + r = session.post(url, json.dumps(issue), timeout=HTTP_TIMEOUT) if r.status_code == 201: g.log.info("new GitHub issue created") diff --git a/tests/test_auth/test_controllers.py b/tests/test_auth/test_controllers.py index 3028599b7..fcf6288b9 100644 --- a/tests/test_auth/test_controllers.py +++ b/tests/test_auth/test_controllers.py @@ -252,6 +252,7 @@ def test_github_callback_incomplete_get(self, mock_post, mock_g, mock_user_model self.assertEqual(response.status_code, 302) mock_post.assert_called_once() + self.assertEqual(mock_post.call_args.kwargs.get('timeout'), (3.05, 10)) mock_user_model.query.filter.assert_called_once() mock_g.db.commit.assert_not_called() mock_g.log.error.assert_called_once_with("GitHub didn't return an access token") @@ -306,6 +307,8 @@ def test_github_token_validity(self, mock_post): mock_post.return_value = MockResponse({}, 404) res = github_token_validity('token') self.assertEqual(res, False) + mock_post.assert_called_once() + self.assertEqual(mock_post.call_args.kwargs.get('timeout'), (3.05, 10)) class ManageAccount(BaseTestCase): diff --git a/tests/test_mailer.py b/tests/test_mailer.py index 60617b631..d1d58f43c 100755 --- a/tests/test_mailer.py +++ b/tests/test_mailer.py @@ -38,4 +38,5 @@ def test_that_send_simple_message_creates_the_appropriate_request(self): mock_post.assert_called_once_with("%s/messages" % mailer.api_url, auth=mailer.auth, - data=expected_data) + data=expected_data, + timeout=(3.05, 10)) diff --git a/tests/test_upload/test_controllers.py b/tests/test_upload/test_controllers.py index de8772ffc..eecb4fca9 100644 --- a/tests/test_upload/test_controllers.py +++ b/tests/test_upload/test_controllers.py @@ -11,6 +11,23 @@ from tests.base import BaseTestCase, MockResponse +class TestMakeGithubIssue(BaseTestCase): + """Test GitHub issue creation HTTP timeout.""" + + @mock.patch('mod_upload.controllers.g') + @mock.patch('requests.Session.post') + def test_make_github_issue_passes_timeout(self, mock_post, mock_g): + """make_github_issue must not hang if GitHub never responds.""" + mock_post.return_value = MockResponse({'number': 1}, 201) + + from mod_upload.controllers import make_github_issue + + make_github_issue('title', body='body', labels=['bug']) + + mock_post.assert_called_once() + self.assertEqual(mock_post.call_args.kwargs.get('timeout'), (3.05, 10)) + + class TestControllers(BaseTestCase): """Test upload-related cases.""" diff --git a/tests/test_utility.py b/tests/test_utility.py index db9d8fe80..73ca39db4 100644 --- a/tests/test_utility.py +++ b/tests/test_utility.py @@ -27,7 +27,8 @@ def test_get_cached_web_hook_blocks_invalid_response(self, mock_get, mock_critic cached_web_hook_blocks = get_cached_web_hook_blocks() - mock_get.assert_called_once() + mock_get.assert_called_once_with( + 'https://api.github.com/meta', auth=mock.ANY, timeout=(3.05, 10)) mock_critical.assert_called_once_with("Failed to retrieve hook IP's from GitHub! API returned {}") @mock.patch('flask.g.log') diff --git a/utility.py b/utility.py index ecc8df7e5..a93135617 100644 --- a/utility.py +++ b/utility.py @@ -15,6 +15,9 @@ ROOT_DIR = path.dirname(path.abspath(__file__)) +# Connect, read. Same tuple already used by fetch_username_from_token. +HTTP_TIMEOUT = (3.05, 10) + def serve_file_download(file_name, file_folder, file_sub_folder='') -> werkzeug.wrappers.response.Response: """ @@ -138,7 +141,8 @@ def get_cached_web_hook_blocks() -> List[str]: client_id = config.get('GITHUB_CLIENT_ID', '') client_secret = config.get('GITHUB_CLIENT_KEY', '') meta_json = requests.get( - 'https://api.github.com/meta', auth=(client_id, client_secret)).json() + 'https://api.github.com/meta', auth=(client_id, client_secret), + timeout=HTTP_TIMEOUT).json() try: cached_web_hook_blocks = meta_json['hooks'] # We successfully fetched the IPs so we reset the clock