Skip to content
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

Support credentials from environment variables. #6723

Closed
wants to merge 3 commits 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
10 changes: 10 additions & 0 deletions docs/html/reference/pip_install.rst
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,16 @@ Options
.. pip-index-options::


Environment Variables
*********************

It is possible to specify a username and password for accessing a pip repository which requires authentication.

Use the environment variables ``PIP_USERNAME`` and ``PIP_PASSWORD`` to specify credentials.

Note that these credentials will be used for all indexes. See the other means of specifying credentials if you use multiple indexes requiring different credentials.


.. _`pip install Examples`:

Examples
Expand Down
1 change: 1 addition & 0 deletions news/4789.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Allow credentials from environment variables 'PIP_USERNAME' and 'PIP_PASSWORD'.
16 changes: 16 additions & 0 deletions src/pip/_internal/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,22 @@ def _get_new_credentials(self, original_url, allow_netrc=True,
logger.debug("Found credentials in index url for %s", netloc)
return index_url_user_password

# Check for creds in environment
user_var = 'PIP_USERNAME'
passwd_var = 'PIP_PASSWORD'
msg = "Found '{fv}' env variable, but not '{nv}'. Ignoring '{fv}'."
env_username = os.environ.get(user_var)
env_password = os.environ.get(passwd_var)
if env_username:
if env_password:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not put this restriction - there were many people in #6796 that have authentication consisting of a single token.

logger.debug("Found credentials in environment.")
return env_username, env_password

logger.warning(msg.format(fv=user_var, nv=passwd_var))

if env_password:
logger.warning(msg.format(fv=passwd_var, nv=user_var))

# Get creds from netrc if we still don't have them
if allow_netrc:
netrc_auth = get_netrc_auth(original_url)
Expand Down
67 changes: 67 additions & 0 deletions tests/unit/test_download.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import functools
import hashlib
import logging
import os
import sys
from io import BytesIO
Expand Down Expand Up @@ -512,6 +513,72 @@ def test_get_index_url_credentials():
assert get("http://example.com/path3/path2") == (None, None)


def test_get_env_credentials(request, caplog):
def reset_env():
del os.environ['PIP_USERNAME']
del os.environ['PIP_PASSWORD']

request.addfinalizer(reset_env)

os.environ['PIP_USERNAME'] = 'foo'
os.environ['PIP_PASSWORD'] = 'bar'

with caplog.at_level(logging.DEBUG):
auth = MultiDomainBasicAuth()
get = functools.partial(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would just call auth._get_new_credentials directly and store the result in an intermediate variable. If this ever fails the assertion output looks better as well since it would be retrieved_credentials == ('foo', 'bar') which is a bit easier to understand than get, IMO.

auth._get_new_credentials,
allow_netrc=False,
allow_keyring=False
)

# Check resolution of credentials from env
assert get("http://envexample.com/pathx") == ('foo', 'bar')

assert "Found credentials in environment" in caplog.text


def test_get_env_credentials_only_username(request, caplog):
def reset_env():
del os.environ['PIP_USERNAME']

request.addfinalizer(reset_env)

os.environ['PIP_USERNAME'] = 'foo'

auth = MultiDomainBasicAuth()
get = functools.partial(
auth._get_new_credentials,
allow_netrc=False,
allow_keyring=False
)

# Check resolution of credentials from env
assert get("http://envexample.com/pathx") == (None, None)

assert "but not 'PIP_PASSWORD'" in caplog.text


def test_get_env_credentials_only_password(request, caplog):
def reset_env():
del os.environ['PIP_PASSWORD']

request.addfinalizer(reset_env)

os.environ['PIP_PASSWORD'] = 'bar'

auth = MultiDomainBasicAuth()
get = functools.partial(
auth._get_new_credentials,
allow_netrc=False,
allow_keyring=False
)

# Check resolution of credentials from env
assert get("http://envexample.com/pathx") == (None, None)

assert "but not 'PIP_USERNAME'" in caplog.text


class KeyringModuleV1(object):
"""Represents the supported API of keyring before get_credential
was added.
Expand Down