Repository: pypa/pip

Issue:
--help output does not redact password from configured index_url
**Environment**

* pip version: 20.2.4
* Python version: 3.8.6
* OS: Windows 10

<!-- Feel free to add more information about your environment here -->

**Description**

The help text for at least the `install` and `wheel` subcommands (likely others too) display the full configured index URL as a default, without redacting credentials:

```
Package Index Options:
  -i, --index-url <url>       Base URL of the Python Package Index (default https://us
                              er:SuperSecret@mypypi.example.com/api/pypi/simple). This
                              should point to a repository compliant with PEP 503 (the
                              simple repository API) or a local directory laid out in
                              the same format.
```

The URL is coming from pip.ini in `C:\Users\cdunklau\AppData\Roaming\pip\pip.ini`:

```
[global]
index-url = https://user:SuperSecret@mypypi.example.com/api/pypi/simple
```

**Expected behavior**

Pip should redact the password, as it does in log entries involving a custom index URL, e.g.:

```
Package Index Options:
  -i, --index-url <url>       Base URL of the Python Package Index (default https://us
                              er:****@mypypi.example.com/api/pypi/simple). This
                              should point to a repository compliant with PEP 503 (the
                              simple repository API) or a local directory laid out in
                              the same format.
```

**How to Reproduce**

1. Configure a custom index-url in pip.ini with credentials, as above
2. Run `pip install --help`
3. Observe the password is not redacted in the help text


Discussion:

Resolving patch:
diff --git a/src/pip/_internal/cli/parser.py b/src/pip/_internal/cli/parser.py
--- a/src/pip/_internal/cli/parser.py
+++ b/src/pip/_internal/cli/parser.py
@@ -17,6 +17,7 @@
 from pip._internal.cli.status_codes import UNKNOWN_ERROR
 from pip._internal.configuration import Configuration, ConfigurationError
 from pip._internal.utils.compat import get_terminal_size
+from pip._internal.utils.misc import redact_auth_from_url
 
 logger = logging.getLogger(__name__)
 
@@ -106,12 +107,22 @@ class UpdatingDefaultsHelpFormatter(PrettyHelpFormatter):
 
     This is updates the defaults before expanding them, allowing
     them to show up correctly in the help listing.
+
+    Also redact auth from url type options
     """
 
     def expand_default(self, option):
+        default_value = None
         if self.parser is not None:
             self.parser._update_defaults(self.parser.defaults)
-        return optparse.IndentedHelpFormatter.expand_default(self, option)
+            default_value = self.parser.defaults.get(option.dest)
+        help_text = optparse.IndentedHelpFormatter.expand_default(self, option)
+
+        if default_value and option.metavar == 'URL':
+            help_text = help_text.replace(
+                default_value, redact_auth_from_url(default_value))
+
+        return help_text
 
 
 class CustomOptionParser(optparse.OptionParser):


Test patch:
diff --git a/tests/functional/test_help.py b/tests/functional/test_help.py
--- a/tests/functional/test_help.py
+++ b/tests/functional/test_help.py
@@ -64,6 +64,16 @@ def test_help_command_should_exit_status_error_when_cmd_does_not_exist(script):
     assert result.returncode == ERROR
 
 
+def test_help_command_redact_auth_from_url(script):
+    """
+    Test `help` on various subcommands redact auth from url
+    """
+    script.environ['PIP_INDEX_URL'] = 'https://user:secret@example.com'
+    result = script.pip('install', '--help')
+    assert result.returncode == SUCCESS
+    assert 'secret' not in result.stdout
+
+
 def test_help_commands_equally_functional(in_memory_pip):
     """
     Test if `pip help` and 'pip --help' behave the same way.