Skip to content

Commit

Permalink
Merge pull request #11698 from Darsstar/keyring-multi-choice
Browse files Browse the repository at this point in the history
  • Loading branch information
uranusjr committed Mar 15, 2023
2 parents 85eb40d + 6affad8 commit 5c3d1fe
Show file tree
Hide file tree
Showing 9 changed files with 498 additions and 105 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ repos:
additional_dependencies: [
'keyring==23.0.1',
'nox==2021.6.12',
'pytest==7.1.1',
'pytest',
'types-docutils==0.18.3',
'types-setuptools==57.4.14',
'types-freezegun==1.1.9',
Expand Down
115 changes: 105 additions & 10 deletions docs/html/topics/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,25 +66,120 @@ man pages][netrc-docs].
## Keyring Support

pip supports loading credentials stored in your keyring using the
{pypi}`keyring` library.
{pypi}`keyring` library, which can be enabled py passing `--keyring-provider`
with a value of `auto`, `disabled`, `import`, or `subprocess`. The default
value `auto` respects `--no-input` and not query keyring at all if the option
is used; otherwise it tries the `import`, `subprocess`, and `disabled`
providers (in this order) and uses the first one that works.

pip will first try to use `keyring` in the same environment as itself and
fallback to using any `keyring` installation which is available on `PATH`.
### Configuring pip's keyring usage

Therefore, either of the following setups will work:
Since the keyring configuration is likely system-wide, a more common way to
configure its usage would be to use a configuration instead:

```{seealso}
{doc}`./configuration` describes how pip configuration works.
```

```bash
$ pip install keyring # install keyring from PyPI into same environment as pip
$ echo "your-password" | keyring set pypi.company.com your-username
$ pip install your-package --index-url https://pypi.company.com/
$ pip config set --global global.keyring-provider subprocess

# A different user on the same system which has PYTHONPATH configured and and
# wanting to use keyring installed that way could then run
$ pip config set --user global.keyring-provider import

# For a specific virtual environment you might want to use disable it again
# because you will only be using PyPI and the private repo (and mirror)
# requires 2FA with a keycard and a pincode
$ pip config set --site global.index https://pypi.org/simple
$ pip config set --site global.keyring-provider disabled

# configuring it via environment variable is also possible
$ export PIP_KEYRING_PROVIDER=disabled
```

or
### Using keyring's Python module

Setting `keyring-provider` to `import` makes pip communicate with `keyring` via
its Python interface.

```bash
$ pipx install keyring # install keyring from PyPI into standalone environment
# install keyring from PyPI
$ pip install keyring --index-url https://pypi.org/simple
$ echo "your-password" | keyring set pypi.company.com your-username
$ pip install your-package --index-url https://pypi.company.com/
$ pip install your-package --keyring-provider import --index-url https://pypi.company.com/
```

### Using keyring as a command line application

Setting `keyring-provider` to `subprocess` makes pip look for and use the
`keyring` command found on `PATH`.

For this use case, a username *must* be included in the URL, since it is
required by `keyring`'s command line interface. See the example below or the
basic HTTP authentication section at the top of this page.

```bash
# Install keyring from PyPI using pipx, which we assume is installed properly
# you can also create a venv somewhere and add it to the PATH yourself instead
$ pipx install keyring --index-url https://pypi.org/simple

# For Azure DevOps, also install its keyring backend.
$ pipx inject keyring artifacts-keyring --index-url https://pypi.org/simple

# For Google Artifact Registry, also install and initialize its keyring backend.
$ pipx inject keyring keyrings.google-artifactregistry-auth --index-url https://pypi.org/simple
$ gcloud auth login

# Note that a username is required in the index URL.
$ pip install your-package --keyring-provider subprocess --index-url https://username@pypi.example.com/
```

### Here be dragons

The `auto` provider is conservative and does not query keyring at all when
`--no-input` is used because the keyring might require user interaction such as
prompting the user on the console. Third party tools frequently call Pip for
you and do indeed pass `--no-input` as they are well-behaved and don't have
much information to work with. (Keyring does have an api to request a backend
that does not require user input.) You have more information about your system,
however!

You can force keyring usage by requesting a keyring provider other than `auto`
(or `disabled`). Leaving `import` and `subprocess`. You do this by passing
`--keyring-provider import` or one of the following methods:

```bash
# via config file, possibly with --user, --global or --site
$ pip config set global.keyring-provider subprocess
# or via environment variable
$ export PIP_KEYRING_PROVIDER=import
```

```{warning}
Be careful when doing this since it could cause tools such as pipx and Pipenv
to appear to hang. They show their own progress indicator while hiding output
from the subprocess in which they run Pip. You won't know whether the keyring
backend is waiting the user input or not in such situations.
```

pip is conservative and does not query keyring at all when `--no-input` is used
because the keyring might require user interaction such as prompting the user
on the console. You can force keyring usage by passing `--force-keyring` or one
of the following:

```bash
# possibly with --user, --global or --site
$ pip config set global.force-keyring true
# or
$ export PIP_FORCE_KEYRING=1
```

```{warning}
Be careful when doing this since it could cause tools such as pipx and Pipenv
to appear to hang. They show their own progress indicator while hiding output
from the subprocess in which they run Pip. You won't know whether the keyring
backend is waiting the user input or not in such situations.
```

Note that `keyring` (the Python package) needs to be installed separately from
Expand Down
1 change: 1 addition & 0 deletions news/8719.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add ``--keyring-provider`` flag. See the Authentication page in the documentation for more info.
14 changes: 14 additions & 0 deletions src/pip/_internal/cli/cmdoptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,19 @@ class PipOption(Option):
help="Disable prompting for input.",
)

keyring_provider: Callable[..., Option] = partial(
Option,
"--keyring-provider",
dest="keyring_provider",
choices=["auto", "disabled", "import", "subprocess"],
default="disabled",
help=(
"Enable the credential lookup via the keyring library if user input is allowed."
" Specify which mechanism to use [disabled, import, subprocess]."
" (default: disabled)"
),
)

proxy: Callable[..., Option] = partial(
Option,
"--proxy",
Expand Down Expand Up @@ -1028,6 +1041,7 @@ def check_list_path_option(options: Values) -> None:
quiet,
log,
no_input,
keyring_provider,
proxy,
retries,
timeout,
Expand Down
1 change: 1 addition & 0 deletions src/pip/_internal/cli/req_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ def _build_session(

# Determine if we can prompt the user for authentication or not
session.auth.prompting = not options.no_input
session.auth.keyring_provider = options.keyring_provider

return session

Expand Down
Loading

0 comments on commit 5c3d1fe

Please sign in to comment.