Repository: pypa/pip
Pull request #12350 (2023-10-14 12:01:43)

Redact URLs in Collecting... logs
This is a fix for a part of https://github.com/pypa/pip/issues/11410#issuecomment-1742459945


Patch:
diff --git a/news/12350.bugfix.rst b/news/12350.bugfix.rst
new file mode 100644
index 000000000..3fb16b4ed
--- /dev/null
+++ b/news/12350.bugfix.rst
@@ -0,0 +1,1 @@
+Redact password from URLs in some additional places.
diff --git a/src/pip/_internal/operations/prepare.py b/src/pip/_internal/operations/prepare.py
index 1b32d7eec..488e76358 100644
--- a/src/pip/_internal/operations/prepare.py
+++ b/src/pip/_internal/operations/prepare.py
@@ -47,6 +47,7 @@ from pip._internal.utils.misc import (
     display_path,
     hash_file,
     hide_url,
+    redact_auth_from_requirement,
 )
 from pip._internal.utils.temp_dir import TempDirectory
 from pip._internal.utils.unpacking import unpack_file
@@ -277,7 +278,7 @@ class RequirementPreparer:
             information = str(display_path(req.link.file_path))
         else:
             message = "Collecting %s"
-            information = str(req.req or req)
+            information = redact_auth_from_requirement(req.req) if req.req else str(req)
 
         # If we used req.req, inject requirement source if available (this
         # would already be included if we used req directly)
diff --git a/src/pip/_internal/req/req_install.py b/src/pip/_internal/req/req_install.py
index dd8a0db27..e556be2b4 100644
--- a/src/pip/_internal/req/req_install.py
+++ b/src/pip/_internal/req/req_install.py
@@ -49,6 +49,7 @@ from pip._internal.utils.misc import (
     display_path,
     hide_url,
     is_installable_dir,
+    redact_auth_from_requirement,
     redact_auth_from_url,
 )
 from pip._internal.utils.packaging import safe_extra
@@ -188,7 +189,7 @@ class InstallRequirement:
 
     def __str__(self) -> str:
         if self.req:
-            s = str(self.req)
+            s = redact_auth_from_requirement(self.req)
             if self.link:
                 s += " from {}".format(redact_auth_from_url(self.link.url))
         elif self.link:
diff --git a/src/pip/_internal/utils/misc.py b/src/pip/_internal/utils/misc.py
index 9a6353fc8..78060e864 100644
--- a/src/pip/_internal/utils/misc.py
+++ b/src/pip/_internal/utils/misc.py
@@ -35,6 +35,7 @@ from typing import (
     cast,
 )
 
+from pip._vendor.packaging.requirements import Requirement
 from pip._vendor.pyproject_hooks import BuildBackendHookCaller
 from pip._vendor.tenacity import retry, stop_after_delay, wait_fixed
 
@@ -578,6 +579,13 @@ def redact_auth_from_url(url: str) -> str:
     return _transform_url(url, _redact_netloc)[0]
 
 
+def redact_auth_from_requirement(req: Requirement) -> str:
+    """Replace the password in a given requirement url with ****."""
+    if not req.url:
+        return str(req)
+    return str(req).replace(req.url, redact_auth_from_url(req.url))
+
+
 class HiddenText:
     def __init__(self, secret: str, redacted: str) -> None:
         self.secret = secret


Test patch:
diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py
index d3b0d32d1..1352b7664 100644
--- a/tests/unit/test_utils.py
+++ b/tests/unit/test_utils.py
@@ -14,6 +14,7 @@ from typing import Any, Callable, Iterator, List, NoReturn, Optional, Tuple, Typ
 from unittest.mock import Mock, patch
 
 import pytest
+from pip._vendor.packaging.requirements import Requirement
 
 from pip._internal.exceptions import HashMismatch, HashMissing, InstallationError
 from pip._internal.utils.deprecation import PipDeprecationWarning, deprecated
@@ -37,6 +38,7 @@ from pip._internal.utils.misc import (
     normalize_path,
     normalize_version_info,
     parse_netloc,
+    redact_auth_from_requirement,
     redact_auth_from_url,
     redact_netloc,
     remove_auth_from_url,
@@ -765,6 +767,30 @@ def test_redact_auth_from_url(auth_url: str, expected_url: str) -> None:
     assert url == expected_url
 
 
+@pytest.mark.parametrize(
+    "req, expected",
+    [
+        ("pkga", "pkga"),
+        (
+            "resolvelib@ "
+            " git+https://test-user:test-pass@github.com/sarugaku/resolvelib@1.0.1",
+            "resolvelib@"
+            " git+https://test-user:****@github.com/sarugaku/resolvelib@1.0.1",
+        ),
+        (
+            "resolvelib@"
+            " git+https://test-user:test-pass@github.com/sarugaku/resolvelib@1.0.1"
+            " ; python_version>='3.6'",
+            "resolvelib@"
+            " git+https://test-user:****@github.com/sarugaku/resolvelib@1.0.1"
+            ' ; python_version >= "3.6"',
+        ),
+    ],
+)
+def test_redact_auth_from_requirement(req: str, expected: str) -> None:
+    assert redact_auth_from_requirement(Requirement(req)) == expected
+
+
 class TestHiddenText:
     def test_basic(self) -> None:
         """