diff --git a/src/poetry/repositories/http_repository.py b/src/poetry/repositories/http_repository.py index 515907e72e5..6c000ac981f 100644 --- a/src/poetry/repositories/http_repository.py +++ b/src/poetry/repositories/http_repository.py @@ -4,6 +4,7 @@ import hashlib from contextlib import contextmanager +from contextlib import suppress from pathlib import Path from typing import TYPE_CHECKING from typing import Any @@ -374,7 +375,11 @@ def calculate_sha256(self, link: Link) -> str | None: hash_name = get_highest_priority_hash_type( set(link.hashes.keys()), link.filename ) - known_hash = getattr(hashlib, hash_name)() if hash_name else None + known_hash = None + with suppress(ValueError, AttributeError): + # Handle ValueError here as well since under FIPS environments + # this is what is raised (e.g., for MD5) + known_hash = getattr(hashlib, hash_name)() if hash_name else None required_hash = hashlib.sha256() chunksize = 4096 diff --git a/tests/repositories/test_http_repository.py b/tests/repositories/test_http_repository.py index 20475bc2c3a..6d7fd667b02 100644 --- a/tests/repositories/test_http_repository.py +++ b/tests/repositories/test_http_repository.py @@ -171,3 +171,71 @@ def test_get_info_from_wheel_state_sequence(mocker: MockerFixture) -> None: assert mock_metadata_from_wheel_url.call_count == 5 assert mock_download.call_count == 4 + + +@pytest.mark.parametrize( + "mock_hashes", + [ + None, + {"sha256": "e216b70f013c47b82a72540d34347632c5bfe59fd54f5fe5d51f6a68b19aaf84"}, + {"md5": "be7589b4902793e66d7d979bd8581591"}, + ], +) +def test_calculate_sha256( + mocker: MockerFixture, mock_hashes: dict[str, Any] | None +) -> None: + filename = "poetry_core-1.5.0-py3-none-any.whl" + filepath = MockRepository.DIST_FIXTURES / filename + mock_download = mocker.patch( + "poetry.repositories.http_repository.download_file", + side_effect=lambda _, dest, *args, **kwargs: shutil.copy(filepath, dest), + ) + domain = "foo.com" + link = Link(f"https://{domain}/{filename}", hashes=mock_hashes) + repo = MockRepository() + + calculated_hash = repo.calculate_sha256(link) + + assert mock_download.call_count == 1 + assert ( + calculated_hash + == "sha256:e216b70f013c47b82a72540d34347632c5bfe59fd54f5fe5d51f6a68b19aaf84" + ) + + +def test_calculate_sha256_defaults_to_sha256_on_md5_errors( + mocker: MockerFixture, +) -> None: + raised_value_error = False + + def mock_hashlib_md5_error() -> None: + nonlocal raised_value_error + raised_value_error = True + raise ValueError( + "[digital envelope routines: EVP_DigestInit_ex] disabled for FIPS" + ) + + filename = "poetry_core-1.5.0-py3-none-any.whl" + filepath = MockRepository.DIST_FIXTURES / filename + mock_download = mocker.patch( + "poetry.repositories.http_repository.download_file", + side_effect=lambda _, dest, *args, **kwargs: shutil.copy(filepath, dest), + ) + mock_hashlib_md5 = mocker.patch("hashlib.md5", side_effect=mock_hashlib_md5_error) + + domain = "foo.com" + link = Link( + f"https://{domain}/{filename}", + hashes={"md5": "be7589b4902793e66d7d979bd8581591"}, + ) + repo = MockRepository() + + calculated_hash = repo.calculate_sha256(link) + + assert raised_value_error + assert mock_download.call_count == 1 + assert mock_hashlib_md5.call_count == 1 + assert ( + calculated_hash + == "sha256:e216b70f013c47b82a72540d34347632c5bfe59fd54f5fe5d51f6a68b19aaf84" + )