Skip to content

add credential helper support #10394

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions src/pip/_internal/network/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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(
Expand Down
26 changes: 26 additions & 0 deletions tests/unit/test_network_auth.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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:[email protected]/path"])
get = functools.partial(
Expand Down