From 3103bf8d8f63571e197b1aa6df8f8ff462ebe8af Mon Sep 17 00:00:00 2001 From: Laurie O Date: Mon, 27 Jul 2020 20:49:59 +1000 Subject: [PATCH] Check both index URL and its hostname for keyring entries Fixes #8634 --- news/8634.bugfix | 1 + src/pip/_internal/network/auth.py | 19 ++++++++++++------- tests/unit/test_network_auth.py | 14 +++++++++++--- 3 files changed, 24 insertions(+), 10 deletions(-) create mode 100644 news/8634.bugfix diff --git a/news/8634.bugfix b/news/8634.bugfix new file mode 100644 index 00000000000..67a72b73f2a --- /dev/null +++ b/news/8634.bugfix @@ -0,0 +1 @@ +Check both index URL and its hostname for entries in the keyring diff --git a/src/pip/_internal/network/auth.py b/src/pip/_internal/network/auth.py index ca729fcdf5e..fa37070dbfe 100644 --- a/src/pip/_internal/network/auth.py +++ b/src/pip/_internal/network/auth.py @@ -149,14 +149,19 @@ def _get_new_credentials(self, original_url, allow_netrc=True, # If we don't have a password and keyring is available, use it. if allow_keyring: - # The index url is more specific than the netloc, so try it first - kr_auth = ( - get_keyring_auth(index_url, username) or - get_keyring_auth(netloc, username) - ) - if kr_auth: + url_kr = get_keyring_auth(index_url, username) + netloc_kr = get_keyring_auth(netloc, username) + # When using SecretService on keyring >= 19.2.0, a null-password + # credential is always returned, so check the netloc result as well + if (not url_kr and netloc_kr) or ( + url_kr and netloc_kr and netloc_kr[1] and not url_kr[1] + ): logger.debug("Found credentials in keyring for %s", netloc) - return kr_auth + return netloc_kr + elif url_kr: + # The index url is more specific, so it takes priority + logger.debug("Found credentials in keyring for %s", url) + return url_kr return username, password diff --git a/tests/unit/test_network_auth.py b/tests/unit/test_network_auth.py index 08320cfa143..fcfeec136f9 100644 --- a/tests/unit/test_network_auth.py +++ b/tests/unit/test_network_auth.py @@ -212,6 +212,8 @@ def _send(sent_req, **kwargs): class KeyringModuleV2(object): """Represents the current supported API of keyring""" + return_username = False + class Credential(object): def __init__(self, username, password): self.username = username @@ -225,6 +227,10 @@ def get_credential(self, system, username): return self.Credential("username", "url") if system == "example.com": return self.Credential("username", "netloc") + if self.return_username: + # When using SecretService on keyring >= 19.2.0, a null-password + # credential is always returned + return self.Credential(username, None) return None @@ -232,11 +238,13 @@ def get_credential(self, system, username): ("http://example.com/path1", ("username", "netloc")), ("http://example.com/path2/path3", ("username", "url")), ("http://user2@example.com/path2/path3", ("username", "url")), + ("http://ss_user@example.com", ("username", "netloc")), + ("http://ss_user@example2.com", ("ss_user", None)), )) def test_keyring_get_credential(monkeypatch, url, expect): - monkeypatch.setattr( - pip._internal.network.auth, 'keyring', KeyringModuleV2() - ) + keyring = KeyringModuleV2() + keyring.return_username = "ss_user" in url + monkeypatch.setattr(pip._internal.network.auth, 'keyring', keyring) auth = MultiDomainBasicAuth(index_urls=["http://example.com/path2"]) assert auth._get_new_credentials(