diff --git a/news/10269.bugfix.rst b/news/10269.bugfix.rst new file mode 100644 index 00000000000..45aee70d80e --- /dev/null +++ b/news/10269.bugfix.rst @@ -0,0 +1,3 @@ +Fix the auth credential cache to allow for the case in which +the index url contains the username, but the password comes +from an external source, such as keyring. diff --git a/src/pip/_internal/network/auth.py b/src/pip/_internal/network/auth.py index ee27fb67af5..ca42798bd95 100644 --- a/src/pip/_internal/network/auth.py +++ b/src/pip/_internal/network/auth.py @@ -179,9 +179,16 @@ def _get_url_and_credentials( # 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 diff --git a/tests/unit/test_network_auth.py b/tests/unit/test_network_auth.py index 300c953b0d8..7be8941febc 100644 --- a/tests/unit/test_network_auth.py +++ b/tests/unit/test_network_auth.py @@ -78,6 +78,15 @@ def test_get_credentials_uses_cached_credentials(): assert got == expected +def test_get_credentials_uses_cached_credentials_only_username(): + auth = MultiDomainBasicAuth() + auth.passwords["example.com"] = ("user", "pass") + + got = auth._get_url_and_credentials("http://user@example.com/path") + expected = ("http://example.com/path", "user", "pass") + assert got == expected + + def test_get_index_url_credentials(): auth = MultiDomainBasicAuth(index_urls=["http://foo:bar@example.com/path"]) get = functools.partial(