We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Add flag to force pip to always fetch password from keyring
**What's the problem this feature will solve?**

I am trying to fetch modules from a private server. In order to avoid storing this password in filesystem or environment, I have added it to keyring instead. I have also set `https://username@my-private-server.com` as global.index-url in my pip.conf.

Based upon the verbose logs from `pip install`, it seems that pip will first do the request without a password, get a 401, and then fetch the password from keyring.

**Describe the solution you'd like**

There should be a way to force pip to always fetch the password from keyring in the first place. This would prevent it from spamming the server with unauthenticated requests.

**Alternative Solutions**

I don't want to put the password in plaintext in my pip.conf or environment variables, so I'm not aware of any alternative solution.

**Additional context**

n/a


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/pip/_internal/network/auth.py`
Content:
```
1 """Network Authentication Helpers
2 
3 Contains interface (MultiDomainBasicAuth) and associated glue code for
4 providing credentials in the context of network requests.
5 """
6 
7 import urllib.parse
8 from typing import Any, Dict, List, Optional, Tuple
9 
10 from pip._vendor.requests.auth import AuthBase, HTTPBasicAuth
11 from pip._vendor.requests.models import Request, Response
12 from pip._vendor.requests.utils import get_netrc_auth
13 
14 from pip._internal.utils.logging import getLogger
15 from pip._internal.utils.misc import (
16     ask,
17     ask_input,
18     ask_password,
19     remove_auth_from_url,
20     split_auth_netloc_from_url,
21 )
22 from pip._internal.vcs.versioncontrol import AuthInfo
23 
24 logger = getLogger(__name__)
25 
26 Credentials = Tuple[str, str, str]
27 
28 try:
29     import keyring
30 except ImportError:
31     keyring = None  # type: ignore[assignment]
32 except Exception as exc:
33     logger.warning(
34         "Keyring is skipped due to an exception: %s",
35         str(exc),
36     )
37     keyring = None  # type: ignore[assignment]
38 
39 
40 def get_keyring_auth(url: Optional[str], username: Optional[str]) -> Optional[AuthInfo]:
41     """Return the tuple auth for a given url from keyring."""
42     global keyring
43     if not url or not keyring:
44         return None
45 
46     try:
47         try:
48             get_credential = keyring.get_credential
49         except AttributeError:
50             pass
51         else:
52             logger.debug("Getting credentials from keyring for %s", url)
53             cred = get_credential(url, username)
54             if cred is not None:
55                 return cred.username, cred.password
56             return None
57 
58         if username:
59             logger.debug("Getting password from keyring for %s", url)
60             password = keyring.get_password(url, username)
61             if password:
62                 return username, password
63 
64     except Exception as exc:
65         logger.warning(
66             "Keyring is skipped due to an exception: %s",
67             str(exc),
68         )
69         keyring = None  # type: ignore[assignment]
70     return None
71 
72 
73 class MultiDomainBasicAuth(AuthBase):
74     def __init__(
75         self, prompting: bool = True, index_urls: Optional[List[str]] = None
76     ) -> None:
77         self.prompting = prompting
78         self.index_urls = index_urls
79         self.passwords: Dict[str, AuthInfo] = {}
80         # When the user is prompted to enter credentials and keyring is
81         # available, we will offer to save them. If the user accepts,
82         # this value is set to the credentials they entered. After the
83         # request authenticates, the caller should call
84         # ``save_credentials`` to save these.
85         self._credentials_to_save: Optional[Credentials] = None
86 
87     def _get_index_url(self, url: str) -> Optional[str]:
88         """Return the original index URL matching the requested URL.
89 
90         Cached or dynamically generated credentials may work against
91         the original index URL rather than just the netloc.
92 
93         The provided url should have had its username and password
94         removed already. If the original index url had credentials then
95         they will be included in the return value.
96 
97         Returns None if no matching index was found, or if --no-index
98         was specified by the user.
99         """
100         if not url or not self.index_urls:
101             return None
102 
103         for u in self.index_urls:
104             prefix = remove_auth_from_url(u).rstrip("/") + "/"
105             if url.startswith(prefix):
106                 return u
107         return None
108 
109     def _get_new_credentials(
110         self,
111         original_url: str,
112         allow_netrc: bool = True,
113         allow_keyring: bool = False,
114     ) -> AuthInfo:
115         """Find and return credentials for the specified URL."""
116         # Split the credentials and netloc from the url.
117         url, netloc, url_user_password = split_auth_netloc_from_url(
118             original_url,
119         )
120 
121         # Start with the credentials embedded in the url
122         username, password = url_user_password
123         if username is not None and password is not None:
124             logger.debug("Found credentials in url for %s", netloc)
125             return url_user_password
126 
127         # Find a matching index url for this request
128         index_url = self._get_index_url(url)
129         if index_url:
130             # Split the credentials from the url.
131             index_info = split_auth_netloc_from_url(index_url)
132             if index_info:
133                 index_url, _, index_url_user_password = index_info
134                 logger.debug("Found index url %s", index_url)
135 
136         # If an index URL was found, try its embedded credentials
137         if index_url and index_url_user_password[0] is not None:
138             username, password = index_url_user_password
139             if username is not None and password is not None:
140                 logger.debug("Found credentials in index url for %s", netloc)
141                 return index_url_user_password
142 
143         # Get creds from netrc if we still don't have them
144         if allow_netrc:
145             netrc_auth = get_netrc_auth(original_url)
146             if netrc_auth:
147                 logger.debug("Found credentials in netrc for %s", netloc)
148                 return netrc_auth
149 
150         # If we don't have a password and keyring is available, use it.
151         if allow_keyring:
152             # The index url is more specific than the netloc, so try it first
153             # fmt: off
154             kr_auth = (
155                 get_keyring_auth(index_url, username) or
156                 get_keyring_auth(netloc, username)
157             )
158             # fmt: on
159             if kr_auth:
160                 logger.debug("Found credentials in keyring for %s", netloc)
161                 return kr_auth
162 
163         return username, password
164 
165     def _get_url_and_credentials(
166         self, original_url: str
167     ) -> Tuple[str, Optional[str], Optional[str]]:
168         """Return the credentials to use for the provided URL.
169 
170         If allowed, netrc and keyring may be used to obtain the
171         correct credentials.
172 
173         Returns (url_without_credentials, username, password). Note
174         that even if the original URL contains credentials, this
175         function may return a different username and password.
176         """
177         url, netloc, _ = split_auth_netloc_from_url(original_url)
178 
179         # Try to get credentials from original url
180         username, password = self._get_new_credentials(original_url)
181 
182         # If credentials not found, use any stored credentials for this netloc
183         if username is None and password is None:
184             username, password = self.passwords.get(netloc, (None, None))
185 
186         if username is not None or password is not None:
187             # Convert the username and password if they're None, so that
188             # this netloc will show up as "cached" in the conditional above.
189             # Further, HTTPBasicAuth doesn't accept None, so it makes sense to
190             # cache the value that is going to be used.
191             username = username or ""
192             password = password or ""
193 
194             # Store any acquired credentials.
195             self.passwords[netloc] = (username, password)
196 
197         assert (
198             # Credentials were found
199             (username is not None and password is not None)
200             # Credentials were not found
201             or (username is None and password is None)
202         ), f"Could not load credentials from url: {original_url}"
203 
204         return url, username, password
205 
206     def __call__(self, req: Request) -> Request:
207         # Get credentials for this request
208         url, username, password = self._get_url_and_credentials(req.url)
209 
210         # Set the url of the request to the url without any credentials
211         req.url = url
212 
213         if username is not None and password is not None:
214             # Send the basic auth with this request
215             req = HTTPBasicAuth(username, password)(req)
216 
217         # Attach a hook to handle 401 responses
218         req.register_hook("response", self.handle_401)
219 
220         return req
221 
222     # Factored out to allow for easy patching in tests
223     def _prompt_for_password(
224         self, netloc: str
225     ) -> Tuple[Optional[str], Optional[str], bool]:
226         username = ask_input(f"User for {netloc}: ")
227         if not username:
228             return None, None, False
229         auth = get_keyring_auth(netloc, username)
230         if auth and auth[0] is not None and auth[1] is not None:
231             return auth[0], auth[1], False
232         password = ask_password("Password: ")
233         return username, password, True
234 
235     # Factored out to allow for easy patching in tests
236     def _should_save_password_to_keyring(self) -> bool:
237         if not keyring:
238             return False
239         return ask("Save credentials to keyring [y/N]: ", ["y", "n"]) == "y"
240 
241     def handle_401(self, resp: Response, **kwargs: Any) -> Response:
242         # We only care about 401 responses, anything else we want to just
243         #   pass through the actual response
244         if resp.status_code != 401:
245             return resp
246 
247         # We are not able to prompt the user so simply return the response
248         if not self.prompting:
249             return resp
250 
251         parsed = urllib.parse.urlparse(resp.url)
252 
253         # Query the keyring for credentials:
254         username, password = self._get_new_credentials(
255             resp.url,
256             allow_netrc=False,
257             allow_keyring=True,
258         )
259 
260         # Prompt the user for a new username and password
261         save = False
262         if not username and not password:
263             username, password, save = self._prompt_for_password(parsed.netloc)
264 
265         # Store the new username and password to use for future requests
266         self._credentials_to_save = None
267         if username is not None and password is not None:
268             self.passwords[parsed.netloc] = (username, password)
269 
270             # Prompt to save the password to keyring
271             if save and self._should_save_password_to_keyring():
272                 self._credentials_to_save = (parsed.netloc, username, password)
273 
274         # Consume content and release the original connection to allow our new
275         #   request to reuse the same one.
276         resp.content
277         resp.raw.release_conn()
278 
279         # Add our new username and password to the request
280         req = HTTPBasicAuth(username or "", password or "")(resp.request)
281         req.register_hook("response", self.warn_on_401)
282 
283         # On successful request, save the credentials that were used to
284         # keyring. (Note that if the user responded "no" above, this member
285         # is not set and nothing will be saved.)
286         if self._credentials_to_save:
287             req.register_hook("response", self.save_credentials)
288 
289         # Send our new request
290         new_resp = resp.connection.send(req, **kwargs)
291         new_resp.history.append(resp)
292 
293         return new_resp
294 
295     def warn_on_401(self, resp: Response, **kwargs: Any) -> None:
296         """Response callback to warn about incorrect credentials."""
297         if resp.status_code == 401:
298             logger.warning(
299                 "401 Error, Credentials not correct for %s",
300                 resp.request.url,
301             )
302 
303     def save_credentials(self, resp: Response, **kwargs: Any) -> None:
304         """Response callback to save credentials on success."""
305         assert keyring is not None, "should never reach here without keyring"
306         if not keyring:
307             return
308 
309         creds = self._credentials_to_save
310         self._credentials_to_save = None
311         if creds and resp.status_code < 400:
312             try:
313                 logger.info("Saving credentials to keyring")
314                 keyring.set_password(*creds)
315             except Exception:
316                 logger.exception("Failed to save credentials")
317
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/pip/_internal/network/auth.py b/src/pip/_internal/network/auth.py
--- a/src/pip/_internal/network/auth.py
+++ b/src/pip/_internal/network/auth.py
@@ -179,9 +179,16 @@
         # Try to get credentials from original url
         username, password = self._get_new_credentials(original_url)
 
-        # If credentials not found, use any stored credentials for this netloc
-        if username is None and password is None:
-            username, password = self.passwords.get(netloc, (None, None))
+        # If credentials not found, use any stored credentials for this netloc.
+        # Do this if either the username or the password is missing.
+        # This accounts for the situation in which the user has specified
+        # the username in the index url, but the password comes from keyring.
+        if (username is None or password is None) and netloc in self.passwords:
+            un, pw = self.passwords[netloc]
+            # It is possible that the cached credentials are for a different username,
+            # in which case the cache should be ignored.
+            if username is None or username == un:
+                username, password = un, pw
 
         if username is not None or password is not None:
             # Convert the username and password if they're None, so that

{"golden_diff": "diff --git a/src/pip/_internal/network/auth.py b/src/pip/_internal/network/auth.py\n--- a/src/pip/_internal/network/auth.py\n+++ b/src/pip/_internal/network/auth.py\n@@ -179,9 +179,16 @@\n         # Try to get credentials from original url\n         username, password = self._get_new_credentials(original_url)\n \n-        # If credentials not found, use any stored credentials for this netloc\n-        if username is None and password is None:\n-            username, password = self.passwords.get(netloc, (None, None))\n+        # If credentials not found, use any stored credentials for this netloc.\n+        # Do this if either the username or the password is missing.\n+        # This accounts for the situation in which the user has specified\n+        # the username in the index url, but the password comes from keyring.\n+        if (username is None or password is None) and netloc in self.passwords:\n+            un, pw = self.passwords[netloc]\n+            # It is possible that the cached credentials are for a different username,\n+            # in which case the cache should be ignored.\n+            if username is None or username == un:\n+                username, password = un, pw\n \n         if username is not None or password is not None:\n             # Convert the username and password if they're None, so that\n", "issue": "Add flag to force pip to always fetch password from keyring\n**What's the problem this feature will solve?**\r\n\r\nI am trying to fetch modules from a private server. In order to avoid storing this password in filesystem or environment, I have added it to keyring instead. I have also set `https://username@my-private-server.com` as global.index-url in my pip.conf.\r\n\r\nBased upon the verbose logs from `pip install`, it seems that pip will first do the request without a password, get a 401, and then fetch the password from keyring.\r\n\r\n**Describe the solution you'd like**\r\n\r\nThere should be a way to force pip to always fetch the password from keyring in the first place. This would prevent it from spamming the server with unauthenticated requests.\r\n\r\n**Alternative Solutions**\r\n\r\nI don't want to put the password in plaintext in my pip.conf or environment variables, so I'm not aware of any alternative solution.\r\n\r\n**Additional context**\r\n\r\nn/a\r\n\n", "before_files": [{"content": "\"\"\"Network Authentication Helpers\n\nContains interface (MultiDomainBasicAuth) and associated glue code for\nproviding credentials in the context of network requests.\n\"\"\"\n\nimport urllib.parse\nfrom typing import Any, Dict, List, Optional, Tuple\n\nfrom pip._vendor.requests.auth import AuthBase, HTTPBasicAuth\nfrom pip._vendor.requests.models import Request, Response\nfrom pip._vendor.requests.utils import get_netrc_auth\n\nfrom pip._internal.utils.logging import getLogger\nfrom pip._internal.utils.misc import (\n    ask,\n    ask_input,\n    ask_password,\n    remove_auth_from_url,\n    split_auth_netloc_from_url,\n)\nfrom pip._internal.vcs.versioncontrol import AuthInfo\n\nlogger = getLogger(__name__)\n\nCredentials = Tuple[str, str, str]\n\ntry:\n    import keyring\nexcept ImportError:\n    keyring = None  # type: ignore[assignment]\nexcept Exception as exc:\n    logger.warning(\n        \"Keyring is skipped due to an exception: %s\",\n        str(exc),\n    )\n    keyring = None  # type: ignore[assignment]\n\n\ndef get_keyring_auth(url: Optional[str], username: Optional[str]) -> Optional[AuthInfo]:\n    \"\"\"Return the tuple auth for a given url from keyring.\"\"\"\n    global keyring\n    if not url or not keyring:\n        return None\n\n    try:\n        try:\n            get_credential = keyring.get_credential\n        except AttributeError:\n            pass\n        else:\n            logger.debug(\"Getting credentials from keyring for %s\", url)\n            cred = get_credential(url, username)\n            if cred is not None:\n                return cred.username, cred.password\n            return None\n\n        if username:\n            logger.debug(\"Getting password from keyring for %s\", url)\n            password = keyring.get_password(url, username)\n            if password:\n                return username, password\n\n    except Exception as exc:\n        logger.warning(\n            \"Keyring is skipped due to an exception: %s\",\n            str(exc),\n        )\n        keyring = None  # type: ignore[assignment]\n    return None\n\n\nclass MultiDomainBasicAuth(AuthBase):\n    def __init__(\n        self, prompting: bool = True, index_urls: Optional[List[str]] = None\n    ) -> None:\n        self.prompting = prompting\n        self.index_urls = index_urls\n        self.passwords: Dict[str, AuthInfo] = {}\n        # When the user is prompted to enter credentials and keyring is\n        # available, we will offer to save them. If the user accepts,\n        # this value is set to the credentials they entered. After the\n        # request authenticates, the caller should call\n        # ``save_credentials`` to save these.\n        self._credentials_to_save: Optional[Credentials] = None\n\n    def _get_index_url(self, url: str) -> Optional[str]:\n        \"\"\"Return the original index URL matching the requested URL.\n\n        Cached or dynamically generated credentials may work against\n        the original index URL rather than just the netloc.\n\n        The provided url should have had its username and password\n        removed already. If the original index url had credentials then\n        they will be included in the return value.\n\n        Returns None if no matching index was found, or if --no-index\n        was specified by the user.\n        \"\"\"\n        if not url or not self.index_urls:\n            return None\n\n        for u in self.index_urls:\n            prefix = remove_auth_from_url(u).rstrip(\"/\") + \"/\"\n            if url.startswith(prefix):\n                return u\n        return None\n\n    def _get_new_credentials(\n        self,\n        original_url: str,\n        allow_netrc: bool = True,\n        allow_keyring: bool = False,\n    ) -> AuthInfo:\n        \"\"\"Find and return credentials for the specified URL.\"\"\"\n        # Split the credentials and netloc from the url.\n        url, netloc, url_user_password = split_auth_netloc_from_url(\n            original_url,\n        )\n\n        # Start with the credentials embedded in the url\n        username, password = url_user_password\n        if username is not None and password is not None:\n            logger.debug(\"Found credentials in url for %s\", netloc)\n            return url_user_password\n\n        # Find a matching index url for this request\n        index_url = self._get_index_url(url)\n        if index_url:\n            # Split the credentials from the url.\n            index_info = split_auth_netloc_from_url(index_url)\n            if index_info:\n                index_url, _, index_url_user_password = index_info\n                logger.debug(\"Found index url %s\", index_url)\n\n        # If an index URL was found, try its embedded credentials\n        if index_url and index_url_user_password[0] is not None:\n            username, password = index_url_user_password\n            if username is not None and password is not None:\n                logger.debug(\"Found credentials in index url for %s\", netloc)\n                return index_url_user_password\n\n        # Get creds from netrc if we still don't have them\n        if allow_netrc:\n            netrc_auth = get_netrc_auth(original_url)\n            if netrc_auth:\n                logger.debug(\"Found credentials in netrc for %s\", netloc)\n                return netrc_auth\n\n        # If we don't have a password and keyring is available, use it.\n        if allow_keyring:\n            # The index url is more specific than the netloc, so try it first\n            # fmt: off\n            kr_auth = (\n                get_keyring_auth(index_url, username) or\n                get_keyring_auth(netloc, username)\n            )\n            # fmt: on\n            if kr_auth:\n                logger.debug(\"Found credentials in keyring for %s\", netloc)\n                return kr_auth\n\n        return username, password\n\n    def _get_url_and_credentials(\n        self, original_url: str\n    ) -> Tuple[str, Optional[str], Optional[str]]:\n        \"\"\"Return the credentials to use for the provided URL.\n\n        If allowed, netrc and keyring may be used to obtain the\n        correct credentials.\n\n        Returns (url_without_credentials, username, password). Note\n        that even if the original URL contains credentials, this\n        function may return a different username and password.\n        \"\"\"\n        url, netloc, _ = split_auth_netloc_from_url(original_url)\n\n        # Try to get credentials from original url\n        username, password = self._get_new_credentials(original_url)\n\n        # If credentials not found, use any stored credentials for this netloc\n        if username is None and password is None:\n            username, password = self.passwords.get(netloc, (None, None))\n\n        if username is not None or password is not None:\n            # Convert the username and password if they're None, so that\n            # this netloc will show up as \"cached\" in the conditional above.\n            # Further, HTTPBasicAuth doesn't accept None, so it makes sense to\n            # cache the value that is going to be used.\n            username = username or \"\"\n            password = password or \"\"\n\n            # Store any acquired credentials.\n            self.passwords[netloc] = (username, password)\n\n        assert (\n            # Credentials were found\n            (username is not None and password is not None)\n            # Credentials were not found\n            or (username is None and password is None)\n        ), f\"Could not load credentials from url: {original_url}\"\n\n        return url, username, password\n\n    def __call__(self, req: Request) -> Request:\n        # Get credentials for this request\n        url, username, password = self._get_url_and_credentials(req.url)\n\n        # Set the url of the request to the url without any credentials\n        req.url = url\n\n        if username is not None and password is not None:\n            # Send the basic auth with this request\n            req = HTTPBasicAuth(username, password)(req)\n\n        # Attach a hook to handle 401 responses\n        req.register_hook(\"response\", self.handle_401)\n\n        return req\n\n    # Factored out to allow for easy patching in tests\n    def _prompt_for_password(\n        self, netloc: str\n    ) -> Tuple[Optional[str], Optional[str], bool]:\n        username = ask_input(f\"User for {netloc}: \")\n        if not username:\n            return None, None, False\n        auth = get_keyring_auth(netloc, username)\n        if auth and auth[0] is not None and auth[1] is not None:\n            return auth[0], auth[1], False\n        password = ask_password(\"Password: \")\n        return username, password, True\n\n    # Factored out to allow for easy patching in tests\n    def _should_save_password_to_keyring(self) -> bool:\n        if not keyring:\n            return False\n        return ask(\"Save credentials to keyring [y/N]: \", [\"y\", \"n\"]) == \"y\"\n\n    def handle_401(self, resp: Response, **kwargs: Any) -> Response:\n        # We only care about 401 responses, anything else we want to just\n        #   pass through the actual response\n        if resp.status_code != 401:\n            return resp\n\n        # We are not able to prompt the user so simply return the response\n        if not self.prompting:\n            return resp\n\n        parsed = urllib.parse.urlparse(resp.url)\n\n        # Query the keyring for credentials:\n        username, password = self._get_new_credentials(\n            resp.url,\n            allow_netrc=False,\n            allow_keyring=True,\n        )\n\n        # Prompt the user for a new username and password\n        save = False\n        if not username and not password:\n            username, password, save = self._prompt_for_password(parsed.netloc)\n\n        # Store the new username and password to use for future requests\n        self._credentials_to_save = None\n        if username is not None and password is not None:\n            self.passwords[parsed.netloc] = (username, password)\n\n            # Prompt to save the password to keyring\n            if save and self._should_save_password_to_keyring():\n                self._credentials_to_save = (parsed.netloc, username, password)\n\n        # Consume content and release the original connection to allow our new\n        #   request to reuse the same one.\n        resp.content\n        resp.raw.release_conn()\n\n        # Add our new username and password to the request\n        req = HTTPBasicAuth(username or \"\", password or \"\")(resp.request)\n        req.register_hook(\"response\", self.warn_on_401)\n\n        # On successful request, save the credentials that were used to\n        # keyring. (Note that if the user responded \"no\" above, this member\n        # is not set and nothing will be saved.)\n        if self._credentials_to_save:\n            req.register_hook(\"response\", self.save_credentials)\n\n        # Send our new request\n        new_resp = resp.connection.send(req, **kwargs)\n        new_resp.history.append(resp)\n\n        return new_resp\n\n    def warn_on_401(self, resp: Response, **kwargs: Any) -> None:\n        \"\"\"Response callback to warn about incorrect credentials.\"\"\"\n        if resp.status_code == 401:\n            logger.warning(\n                \"401 Error, Credentials not correct for %s\",\n                resp.request.url,\n            )\n\n    def save_credentials(self, resp: Response, **kwargs: Any) -> None:\n        \"\"\"Response callback to save credentials on success.\"\"\"\n        assert keyring is not None, \"should never reach here without keyring\"\n        if not keyring:\n            return\n\n        creds = self._credentials_to_save\n        self._credentials_to_save = None\n        if creds and resp.status_code < 400:\n            try:\n                logger.info(\"Saving credentials to keyring\")\n                keyring.set_password(*creds)\n            except Exception:\n                logger.exception(\"Failed to save credentials\")\n", "path": "src/pip/_internal/network/auth.py"}], "after_files": [{"content": "\"\"\"Network Authentication Helpers\n\nContains interface (MultiDomainBasicAuth) and associated glue code for\nproviding credentials in the context of network requests.\n\"\"\"\n\nimport urllib.parse\nfrom typing import Any, Dict, List, Optional, Tuple\n\nfrom pip._vendor.requests.auth import AuthBase, HTTPBasicAuth\nfrom pip._vendor.requests.models import Request, Response\nfrom pip._vendor.requests.utils import get_netrc_auth\n\nfrom pip._internal.utils.logging import getLogger\nfrom pip._internal.utils.misc import (\n    ask,\n    ask_input,\n    ask_password,\n    remove_auth_from_url,\n    split_auth_netloc_from_url,\n)\nfrom pip._internal.vcs.versioncontrol import AuthInfo\n\nlogger = getLogger(__name__)\n\nCredentials = Tuple[str, str, str]\n\ntry:\n    import keyring\nexcept ImportError:\n    keyring = None  # type: ignore[assignment]\nexcept Exception as exc:\n    logger.warning(\n        \"Keyring is skipped due to an exception: %s\",\n        str(exc),\n    )\n    keyring = None  # type: ignore[assignment]\n\n\ndef get_keyring_auth(url: Optional[str], username: Optional[str]) -> Optional[AuthInfo]:\n    \"\"\"Return the tuple auth for a given url from keyring.\"\"\"\n    global keyring\n    if not url or not keyring:\n        return None\n\n    try:\n        try:\n            get_credential = keyring.get_credential\n        except AttributeError:\n            pass\n        else:\n            logger.debug(\"Getting credentials from keyring for %s\", url)\n            cred = get_credential(url, username)\n            if cred is not None:\n                return cred.username, cred.password\n            return None\n\n        if username:\n            logger.debug(\"Getting password from keyring for %s\", url)\n            password = keyring.get_password(url, username)\n            if password:\n                return username, password\n\n    except Exception as exc:\n        logger.warning(\n            \"Keyring is skipped due to an exception: %s\",\n            str(exc),\n        )\n        keyring = None  # type: ignore[assignment]\n    return None\n\n\nclass MultiDomainBasicAuth(AuthBase):\n    def __init__(\n        self, prompting: bool = True, index_urls: Optional[List[str]] = None\n    ) -> None:\n        self.prompting = prompting\n        self.index_urls = index_urls\n        self.passwords: Dict[str, AuthInfo] = {}\n        # When the user is prompted to enter credentials and keyring is\n        # available, we will offer to save them. If the user accepts,\n        # this value is set to the credentials they entered. After the\n        # request authenticates, the caller should call\n        # ``save_credentials`` to save these.\n        self._credentials_to_save: Optional[Credentials] = None\n\n    def _get_index_url(self, url: str) -> Optional[str]:\n        \"\"\"Return the original index URL matching the requested URL.\n\n        Cached or dynamically generated credentials may work against\n        the original index URL rather than just the netloc.\n\n        The provided url should have had its username and password\n        removed already. If the original index url had credentials then\n        they will be included in the return value.\n\n        Returns None if no matching index was found, or if --no-index\n        was specified by the user.\n        \"\"\"\n        if not url or not self.index_urls:\n            return None\n\n        for u in self.index_urls:\n            prefix = remove_auth_from_url(u).rstrip(\"/\") + \"/\"\n            if url.startswith(prefix):\n                return u\n        return None\n\n    def _get_new_credentials(\n        self,\n        original_url: str,\n        allow_netrc: bool = True,\n        allow_keyring: bool = False,\n    ) -> AuthInfo:\n        \"\"\"Find and return credentials for the specified URL.\"\"\"\n        # Split the credentials and netloc from the url.\n        url, netloc, url_user_password = split_auth_netloc_from_url(\n            original_url,\n        )\n\n        # Start with the credentials embedded in the url\n        username, password = url_user_password\n        if username is not None and password is not None:\n            logger.debug(\"Found credentials in url for %s\", netloc)\n            return url_user_password\n\n        # Find a matching index url for this request\n        index_url = self._get_index_url(url)\n        if index_url:\n            # Split the credentials from the url.\n            index_info = split_auth_netloc_from_url(index_url)\n            if index_info:\n                index_url, _, index_url_user_password = index_info\n                logger.debug(\"Found index url %s\", index_url)\n\n        # If an index URL was found, try its embedded credentials\n        if index_url and index_url_user_password[0] is not None:\n            username, password = index_url_user_password\n            if username is not None and password is not None:\n                logger.debug(\"Found credentials in index url for %s\", netloc)\n                return index_url_user_password\n\n        # Get creds from netrc if we still don't have them\n        if allow_netrc:\n            netrc_auth = get_netrc_auth(original_url)\n            if netrc_auth:\n                logger.debug(\"Found credentials in netrc for %s\", netloc)\n                return netrc_auth\n\n        # If we don't have a password and keyring is available, use it.\n        if allow_keyring:\n            # The index url is more specific than the netloc, so try it first\n            # fmt: off\n            kr_auth = (\n                get_keyring_auth(index_url, username) or\n                get_keyring_auth(netloc, username)\n            )\n            # fmt: on\n            if kr_auth:\n                logger.debug(\"Found credentials in keyring for %s\", netloc)\n                return kr_auth\n\n        return username, password\n\n    def _get_url_and_credentials(\n        self, original_url: str\n    ) -> Tuple[str, Optional[str], Optional[str]]:\n        \"\"\"Return the credentials to use for the provided URL.\n\n        If allowed, netrc and keyring may be used to obtain the\n        correct credentials.\n\n        Returns (url_without_credentials, username, password). Note\n        that even if the original URL contains credentials, this\n        function may return a different username and password.\n        \"\"\"\n        url, netloc, _ = split_auth_netloc_from_url(original_url)\n\n        # Try to get credentials from original url\n        username, password = self._get_new_credentials(original_url)\n\n        # If credentials not found, use any stored credentials for this netloc.\n        # Do this if either the username or the password is missing.\n        # This accounts for the situation in which the user has specified\n        # the username in the index url, but the password comes from keyring.\n        if (username is None or password is None) and netloc in self.passwords:\n            un, pw = self.passwords[netloc]\n            # It is possible that the cached credentials are for a different username,\n            # in which case the cache should be ignored.\n            if username is None or username == un:\n                username, password = un, pw\n\n        if username is not None or password is not None:\n            # Convert the username and password if they're None, so that\n            # this netloc will show up as \"cached\" in the conditional above.\n            # Further, HTTPBasicAuth doesn't accept None, so it makes sense to\n            # cache the value that is going to be used.\n            username = username or \"\"\n            password = password or \"\"\n\n            # Store any acquired credentials.\n            self.passwords[netloc] = (username, password)\n\n        assert (\n            # Credentials were found\n            (username is not None and password is not None)\n            # Credentials were not found\n            or (username is None and password is None)\n        ), f\"Could not load credentials from url: {original_url}\"\n\n        return url, username, password\n\n    def __call__(self, req: Request) -> Request:\n        # Get credentials for this request\n        url, username, password = self._get_url_and_credentials(req.url)\n\n        # Set the url of the request to the url without any credentials\n        req.url = url\n\n        if username is not None and password is not None:\n            # Send the basic auth with this request\n            req = HTTPBasicAuth(username, password)(req)\n\n        # Attach a hook to handle 401 responses\n        req.register_hook(\"response\", self.handle_401)\n\n        return req\n\n    # Factored out to allow for easy patching in tests\n    def _prompt_for_password(\n        self, netloc: str\n    ) -> Tuple[Optional[str], Optional[str], bool]:\n        username = ask_input(f\"User for {netloc}: \")\n        if not username:\n            return None, None, False\n        auth = get_keyring_auth(netloc, username)\n        if auth and auth[0] is not None and auth[1] is not None:\n            return auth[0], auth[1], False\n        password = ask_password(\"Password: \")\n        return username, password, True\n\n    # Factored out to allow for easy patching in tests\n    def _should_save_password_to_keyring(self) -> bool:\n        if not keyring:\n            return False\n        return ask(\"Save credentials to keyring [y/N]: \", [\"y\", \"n\"]) == \"y\"\n\n    def handle_401(self, resp: Response, **kwargs: Any) -> Response:\n        # We only care about 401 responses, anything else we want to just\n        #   pass through the actual response\n        if resp.status_code != 401:\n            return resp\n\n        # We are not able to prompt the user so simply return the response\n        if not self.prompting:\n            return resp\n\n        parsed = urllib.parse.urlparse(resp.url)\n\n        # Query the keyring for credentials:\n        username, password = self._get_new_credentials(\n            resp.url,\n            allow_netrc=False,\n            allow_keyring=True,\n        )\n\n        # Prompt the user for a new username and password\n        save = False\n        if not username and not password:\n            username, password, save = self._prompt_for_password(parsed.netloc)\n\n        # Store the new username and password to use for future requests\n        self._credentials_to_save = None\n        if username is not None and password is not None:\n            self.passwords[parsed.netloc] = (username, password)\n\n            # Prompt to save the password to keyring\n            if save and self._should_save_password_to_keyring():\n                self._credentials_to_save = (parsed.netloc, username, password)\n\n        # Consume content and release the original connection to allow our new\n        #   request to reuse the same one.\n        resp.content\n        resp.raw.release_conn()\n\n        # Add our new username and password to the request\n        req = HTTPBasicAuth(username or \"\", password or \"\")(resp.request)\n        req.register_hook(\"response\", self.warn_on_401)\n\n        # On successful request, save the credentials that were used to\n        # keyring. (Note that if the user responded \"no\" above, this member\n        # is not set and nothing will be saved.)\n        if self._credentials_to_save:\n            req.register_hook(\"response\", self.save_credentials)\n\n        # Send our new request\n        new_resp = resp.connection.send(req, **kwargs)\n        new_resp.history.append(resp)\n\n        return new_resp\n\n    def warn_on_401(self, resp: Response, **kwargs: Any) -> None:\n        \"\"\"Response callback to warn about incorrect credentials.\"\"\"\n        if resp.status_code == 401:\n            logger.warning(\n                \"401 Error, Credentials not correct for %s\",\n                resp.request.url,\n            )\n\n    def save_credentials(self, resp: Response, **kwargs: Any) -> None:\n        \"\"\"Response callback to save credentials on success.\"\"\"\n        assert keyring is not None, \"should never reach here without keyring\"\n        if not keyring:\n            return\n\n        creds = self._credentials_to_save\n        self._credentials_to_save = None\n        if creds and resp.status_code < 400:\n            try:\n                logger.info(\"Saving credentials to keyring\")\n                keyring.set_password(*creds)\n            except Exception:\n                logger.exception(\"Failed to save credentials\")\n", "path": "src/pip/_internal/network/auth.py"}]}