Skip to content

Commit

Permalink
Merge pull request #11938 from sbidoul/fix-direct-url-hash-trusted-sbi
Browse files Browse the repository at this point in the history
Don't trust link hash in direct URL dependencies
  • Loading branch information
sbidoul authored Apr 10, 2023
2 parents 62e932a + 453a5a7 commit 5d4a974
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 1 deletion.
3 changes: 3 additions & 0 deletions news/11938.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
When package A depends on package B provided as a direct URL dependency including a hash
embedded in the link, the ``--require-hashes`` option did not warn when user supplied hashes
were missing for package B.
7 changes: 6 additions & 1 deletion src/pip/_internal/req/req_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,12 @@ def hashes(self, trust_internet: bool = True) -> Hashes:
"""
good_hashes = self.hash_options.copy()
link = self.link if trust_internet else self.original_link
if trust_internet:
link = self.link
elif self.original_link and self.user_supplied:
link = self.original_link
else:
link = None
if link and link.hash:
good_hashes.setdefault(link.hash_name, []).append(link.hash)
return Hashes(good_hashes)
Expand Down
113 changes: 113 additions & 0 deletions tests/functional/test_install.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import hashlib
import os
import re
import ssl
Expand All @@ -13,6 +14,7 @@
from pip._internal.cli.status_codes import ERROR, SUCCESS
from pip._internal.models.index import PyPI, TestPyPI
from pip._internal.utils.misc import rmtree
from pip._internal.utils.urls import path_to_url
from tests.conftest import CertFactory
from tests.lib import (
PipTestEnvironment,
Expand Down Expand Up @@ -616,6 +618,117 @@ def test_hashed_install_failure(script: PipTestEnvironment, tmpdir: Path) -> Non
assert len(result.files_created) == 0


def test_link_hash_pass_require_hashes(
script: PipTestEnvironment, shared_data: TestData
) -> None:
"""Test that a good hash in user provided direct URL is
considered valid for --require-hashes."""
url = path_to_url(str(shared_data.packages.joinpath("simple-1.0.tar.gz")))
url = (
f"{url}#sha256="
"393043e672415891885c9a2a0929b1af95fb866d6ca016b42d2e6ce53619b653"
)
script.pip_install_local("--no-deps", "--require-hashes", url)


def test_bad_link_hash_install_failure(
script: PipTestEnvironment, shared_data: TestData
) -> None:
"""Test that wrong hash in direct URL stops installation."""
url = path_to_url(str(shared_data.packages.joinpath("simple-1.0.tar.gz")))
url = f"{url}#sha256=invalidhash"
result = script.pip_install_local("--no-deps", url, expect_error=True)
assert "THESE PACKAGES DO NOT MATCH THE HASHES" in result.stderr


def test_bad_link_hash_good_user_hash_install_success(
script: PipTestEnvironment, shared_data: TestData, tmp_path: Path
) -> None:
"""Test that wrong hash in direct URL ignored when good --hash provided.
This behaviour may be accidental?
"""
url = path_to_url(str(shared_data.packages.joinpath("simple-1.0.tar.gz")))
url = f"{url}#sha256=invalidhash"
digest = "393043e672415891885c9a2a0929b1af95fb866d6ca016b42d2e6ce53619b653"
with requirements_file(
f"simple @ {url} --hash sha256:{digest}", tmp_path
) as reqs_file:
script.pip_install_local("--no-deps", "--require-hashes", "-r", reqs_file)


def test_link_hash_in_dep_fails_require_hashes(
script: PipTestEnvironment, tmp_path: Path, shared_data: TestData
) -> None:
"""Test that a good hash in direct URL dependency is not considered
for --require-hashes."""
# Create a project named pkga that depends on the simple-1.0.tar.gz with a direct
# URL including a hash.
simple_url = path_to_url(str(shared_data.packages.joinpath("simple-1.0.tar.gz")))
simple_url_with_hash = (
f"{simple_url}#sha256="
"393043e672415891885c9a2a0929b1af95fb866d6ca016b42d2e6ce53619b653"
)
project_path = tmp_path / "pkga"
project_path.mkdir()
project_path.joinpath("pyproject.toml").write_text(
textwrap.dedent(
f"""\
[project]
name = "pkga"
version = "1.0"
dependencies = ["simple @ {simple_url_with_hash}"]
"""
)
)
# Build a wheel for pkga and compute its hash.
wheelhouse = tmp_path / "wheehouse"
wheelhouse.mkdir()
script.pip("wheel", "--no-deps", "-w", wheelhouse, project_path)
digest = hashlib.sha256(
wheelhouse.joinpath("pkga-1.0-py3-none-any.whl").read_bytes()
).hexdigest()
# Install pkga from a requirements file with hash, using --require-hashes.
# This should fail because we have not provided a hash for the 'simple' dependency.
with requirements_file(f"pkga==1.0 --hash sha256:{digest}", tmp_path) as reqs_file:
result = script.pip(
"install",
"--no-build-isolation",
"--require-hashes",
"--no-index",
"-f",
wheelhouse,
"-r",
reqs_file,
expect_error=True,
)
assert "Hashes are required in --require-hashes mode" in result.stderr


def test_bad_link_hash_in_dep_install_failure(
script: PipTestEnvironment, tmp_path: Path, shared_data: TestData
) -> None:
"""Test that wrong hash in direct URL dependency stops installation."""
url = path_to_url(str(shared_data.packages.joinpath("simple-1.0.tar.gz")))
url = f"{url}#sha256=invalidhash"
project_path = tmp_path / "pkga"
project_path.mkdir()
project_path.joinpath("pyproject.toml").write_text(
textwrap.dedent(
f"""\
[project]
name = "pkga"
version = "1.0"
dependencies = ["simple @ {url}"]
"""
)
)
result = script.pip_install_local(
"--no-build-isolation", project_path, expect_error=True
)
assert "THESE PACKAGES DO NOT MATCH THE HASHES" in result.stderr, result.stderr


def assert_re_match(pattern: str, text: str) -> None:
assert re.search(pattern, text), f"Could not find {pattern!r} in {text!r}"

Expand Down

0 comments on commit 5d4a974

Please sign in to comment.