From aa07d869b8bd2e4b41db47428a54b1ff0a02d2f7 Mon Sep 17 00:00:00 2001 From: Jesse Rittner Date: Tue, 24 Aug 2021 20:50:51 -0400 Subject: [PATCH] add credential helper hook to auth.py --- src/pip/_internal/network/auth.py | 21 +++++++++++++++++++-- tests/unit/test_network_auth.py | 26 ++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/pip/_internal/network/auth.py b/src/pip/_internal/network/auth.py index ee27fb67af5..b823f811aa5 100644 --- a/src/pip/_internal/network/auth.py +++ b/src/pip/_internal/network/auth.py @@ -4,8 +4,9 @@ providing credentials in the context of network requests. """ +import importlib import urllib.parse -from typing import Any, Dict, List, Optional, Tuple +from typing import Any, Dict, List, Mapping, Optional, Tuple from pip._vendor.requests.auth import AuthBase, HTTPBasicAuth from pip._vendor.requests.models import Request, Response @@ -72,7 +73,8 @@ def get_keyring_auth(url: Optional[str], username: Optional[str]) -> Optional[Au class MultiDomainBasicAuth(AuthBase): def __init__( - self, prompting: bool = True, index_urls: Optional[List[str]] = None + self, prompting: bool = True, index_urls: Optional[List[str]] = None, + credential_helpers: Optional[Mapping[str, str]] = None ) -> None: self.prompting = prompting self.index_urls = index_urls @@ -83,6 +85,7 @@ def __init__( # request authenticates, the caller should call # ``save_credentials`` to save these. self._credentials_to_save: Optional[Credentials] = None + self._credential_helpers = credential_helpers or {} def _get_index_url(self, url: str) -> Optional[str]: """Return the original index URL matching the requested URL. @@ -160,6 +163,20 @@ def _get_new_credentials( logger.debug("Found credentials in keyring for %s", netloc) return kr_auth + if username is None and password is None and netloc in self._credential_helpers: + helper_name = self._credential_helpers[netloc] + logger.debug("Using credential helper module %s for %s", helper_name, netloc) + try: + helper = importlib.import_module(helper_name) + if not hasattr(helper, 'get_pip_credentials'): + logger.error("Credential helper module %s is missing get_pip_credentials", helper_name) + else: + username, password = helper.get_pip_credentials(netloc) + logger.debug("Fetched credentials from helper module %s for %s", helper_name, netloc) + return username, password + except ModuleNotFoundError: + logger.error("Credential helper module %s for %s not found", helper_name, netloc) + return username, password def _get_url_and_credentials( diff --git a/tests/unit/test_network_auth.py b/tests/unit/test_network_auth.py index 300c953b0d8..c5ba099a191 100644 --- a/tests/unit/test_network_auth.py +++ b/tests/unit/test_network_auth.py @@ -1,6 +1,8 @@ import functools +import sys import pytest +import unittest.mock import pip._internal.network.auth from pip._internal.network.auth import MultiDomainBasicAuth @@ -78,6 +80,30 @@ def test_get_credentials_uses_cached_credentials(): assert got == expected +def test_get_credentials_with_credential_helper(): + try: + fake_helper = unittest.mock.Mock() + fake_helper.get_pip_credentials = unittest.mock.Mock( + return_value=("username", "password"), + ) + sys.modules["fake_helper"] = fake_helper + + auth = MultiDomainBasicAuth( + credential_helpers={ + "example.com": "fake_helper", + }, + ) + + got = auth._get_url_and_credentials("https://example.com/path") + expected = ("https://example.com/path", "username", "password") + assert got == expected + + assert fake_helper.get_pip_credentials.call_count == 1 + assert fake_helper.get_pip_credentials.call_args == (("example.com",),) + finally: + sys.modules.pop('fake_helper', None) + + def test_get_index_url_credentials(): auth = MultiDomainBasicAuth(index_urls=["http://foo:bar@example.com/path"]) get = functools.partial(