Skip to content

Commit

Permalink
disable keyring per default and only install via an extra
Browse files Browse the repository at this point in the history
  • Loading branch information
radoering committed Nov 22, 2024
1 parent c70cbf4 commit 16a6c18
Show file tree
Hide file tree
Showing 12 changed files with 91 additions and 26 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/.tests-matrix.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,11 @@ jobs:
working-directory: poetry-plugin-export
# Replace the python version to avoid conflicts
# if the plugin still supports a wider range than Poetry itself.
# TODO: Add poetry without keyring extra after a poetry-plugin-export version
# with https://github.com/python-poetry/poetry-plugin-export/pull/304 has been released.
run: |
perl -pi -e 's/^python =.*$/python = "~${{ inputs.python-version }}"/' pyproject.toml
poetry add --lock --group dev ../poetry
poetry add --lock --group dev ../poetry[keyring]
# This step can be removed after having released a poetry-plugin-export version
# that has cffi>=1.17.0 in its lock file.
Expand Down
9 changes: 9 additions & 0 deletions docs/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ Any non-ancient version of `pipx` will do.
```bash
pipx install poetry
```

If you want to use a keyring for storing credentials, you can install Poetry with its `keyring` extra:

```bash
pipx install poetry[keyring]
```

See [Repositories - Configuring credentials]({{< relref "repositories#configuring-credentials" >}})
for more information.
{{< /step >}}
{{< step >}}
**Install Poetry (advanced)**
Expand Down
15 changes: 9 additions & 6 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ across all your projects if incorrectly set.

**Environment Variable**: `POETRY_INSTALLER_ONLY_BINARY`

*Introduced in 1.9.0*
*Introduced in 2.0.0*

When set, this configuration allows users to enforce the use of binary distribution format for all, none or
specific packages.
Expand Down Expand Up @@ -495,7 +495,7 @@ Set repository credentials (`username` and `password`) for `<name>`.
See [Repositories - Configuring credentials]({{< relref "repositories#configuring-credentials" >}})
for more information.

### `pypi-token.<name>`:
### `pypi-token.<name>`

**Type**: `string`

Expand All @@ -505,7 +505,7 @@ Set repository credentials (using an API token) for `<name>`.
See [Repositories - Configuring credentials]({{< relref "repositories#configuring-credentials" >}})
for more information.

### `certificates.<name>.cert`:
### `certificates.<name>.cert`

**Type**: `string | boolean`

Expand All @@ -518,7 +518,7 @@ for more information.
This configuration can be set to `false`, if TLS certificate verification should be skipped for this
repository.

### `certificates.<name>.client-cert`:
### `certificates.<name>.client-cert`

**Type**: `string`

Expand All @@ -528,14 +528,17 @@ Set client certificate for repository `<name>`.
See [Repositories - Configuring credentials - Custom certificate authority]({{< relref "repositories#custom-certificate-authority-and-mutual-tls-authentication" >}})
for more information.

### `keyring.enabled`:
### `keyring.enabled`

**Type**: `boolean`

**Default**: `true`
**Default**: `false`

**Environment Variable**: `POETRY_KEYRING_ENABLED`

*Changed default to `false` in 2.0.0*

Enable the system keyring for storing credentials.
(Requires Poetry to be installed with the `keyring` extra.)
See [Repositories - Configuring credentials]({{< relref "repositories#configuring-credentials" >}})
for more information.
16 changes: 10 additions & 6 deletions docs/repositories.md
Original file line number Diff line number Diff line change
Expand Up @@ -472,16 +472,20 @@ poetry config http-basic.pypi <username> <password>
You can also specify the username and password when using the `publish` command
with the `--username` and `--password` options.

If a system keyring is available and supported, the password is stored to and retrieved from the keyring. In the above example, the credential will be stored using the name `poetry-repository-pypi`. If access to keyring fails or is unsupported, this will fall back to writing the password to the `auth.toml` file along with the username.

Keyring support is enabled using the [keyring library](https://pypi.org/project/keyring/). For more information on supported backends refer to the [library documentation](https://keyring.readthedocs.io/en/latest/?badge=latest).

If you do not want to use the keyring, you can tell Poetry to disable it and store the credentials in plaintext config files:
If a system keyring is available and supported, the password is stored to and retrieved from the keyring.
Otherwise, credentials are stored in plaintext config files.
In order to use keyring, you have to install Poetry with its `keyring` extra (`poetry[keyring]`)
and enable keyring support:

```bash
poetry config keyring.enabled false
poetry config keyring.enabled true
```

In the above example, the credential will be stored using the name `poetry-repository-pypi`.
If access to keyring is disabled, fails or is unsupported, this will fall back to writing the password to the `auth.toml` file along with the username.

Keyring support is enabled using the [keyring library](https://pypi.org/project/keyring/). For more information on supported backends refer to the [library documentation](https://keyring.readthedocs.io/en/latest/?badge=latest).

{{% note %}}

Poetry will fall back to Pip style use of keyring so that backends like
Expand Down
5 changes: 4 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 16 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ dulwich = "^0.22.6"
fastjsonschema = "^2.18.0"
importlib-metadata = { version = ">=4.4", python = "<3.10" }
installer = "^0.7.0"
keyring = "^25.1.0"
keyring = { version = "^25.1.0", optional = true }
# packaging uses calver, so version is unclamped
packaging = ">=24.0"
pkginfo = "^1.10"
Expand All @@ -55,6 +55,9 @@ trove-classifiers = ">=2022.5.19"
virtualenv = "^20.26.6"
xattr = { version = "^1.0.0", markers = "sys_platform == 'darwin'" }

[tool.poetry.extras]
keyring = [ "keyring" ]

[tool.poetry.group.dev.dependencies]
pre-commit = ">=2.10"
# add setuptools for PyCharm
Expand All @@ -66,6 +69,7 @@ setuptools = { version = ">=60", python = "<3.10" }
coverage = ">=7.2.0"
deepdiff = ">=6.3"
httpretty = ">=1.1"
keyring = "*" # version is constrained via extra
jaraco-classes = ">=3.3.1"
pytest = ">=8.0"
pytest-cov = ">=4.0"
Expand Down Expand Up @@ -130,8 +134,19 @@ unfixable = [
"ERA", # do not autoremove commented out code
]

[tool.ruff.lint.per-file-ignores]
# keyring is an extra and must only be imported in password_manager.py
# and even there not globally
# see flake8-tidy-imports.banned-api and .banned-module-level-imports settings
"password_manager.py" = ["TID251"]
"tests/*" = ["TID251", "TID253"]

[tool.ruff.lint.flake8-tidy-imports]
ban-relative-imports = "all"
banned-module-level-imports = ["keyring"] # because it is an extra

[tool.ruff.lint.flake8-tidy-imports.banned-api]
"keyring".msg = "keyring imports are only allowed in password_manager.py"

[tool.ruff.lint.isort]
force-single-line = true
Expand Down
2 changes: 1 addition & 1 deletion src/poetry/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ class Config:
},
"system-git-client": False,
"keyring": {
"enabled": True,
"enabled": False,
},
}

Expand Down
7 changes: 6 additions & 1 deletion src/poetry/utils/password_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,12 @@ def keyring(self) -> PoetryKeyring:

@staticmethod
def warn_plaintext_credentials_stored() -> None:
logger.warning("Using a plaintext file to store credentials")
logger.warning(
"Using a plaintext file to store credentials.\n"
"Install Poetry with its `keyring` extra (`poetry[keyring]`)"
"and enable it (`poetry config keyring.enabled true`)"
" to store credentials securely."
)

def set_pypi_token(self, repo_name: str, token: str) -> None:
if not self.use_keyring:
Expand Down
4 changes: 4 additions & 0 deletions tests/config/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ def test_config_expands_tilde_for_virtualenvs_path(
def test_disabled_keyring_is_unavailable(
config: Config, with_simple_keyring: None, dummy_keyring: DummyBackend
) -> None:
manager = PasswordManager(config)
assert not manager.use_keyring

config.config["keyring"]["enabled"] = True
manager = PasswordManager(config)
assert manager.use_keyring

Expand Down
12 changes: 6 additions & 6 deletions tests/console/commands/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def test_list_displays_default_value_if_not_set(
installer.only-binary = null
installer.parallel = true
installer.re-resolve = true
keyring.enabled = true
keyring.enabled = false
requests.max-retries = 0
solver.lazy-wheel = true
system-git-client = false
Expand Down Expand Up @@ -92,7 +92,7 @@ def test_list_displays_set_get_setting(
installer.only-binary = null
installer.parallel = true
installer.re-resolve = true
keyring.enabled = true
keyring.enabled = false
requests.max-retries = 0
solver.lazy-wheel = true
system-git-client = false
Expand Down Expand Up @@ -145,7 +145,7 @@ def test_unset_setting(
installer.only-binary = null
installer.parallel = true
installer.re-resolve = true
keyring.enabled = true
keyring.enabled = false
requests.max-retries = 0
solver.lazy-wheel = true
system-git-client = false
Expand Down Expand Up @@ -176,7 +176,7 @@ def test_unset_repo_setting(
installer.only-binary = null
installer.parallel = true
installer.re-resolve = true
keyring.enabled = true
keyring.enabled = false
requests.max-retries = 0
solver.lazy-wheel = true
system-git-client = false
Expand Down Expand Up @@ -305,7 +305,7 @@ def test_list_displays_set_get_local_setting(
installer.only-binary = null
installer.parallel = true
installer.re-resolve = true
keyring.enabled = true
keyring.enabled = false
requests.max-retries = 0
solver.lazy-wheel = true
system-git-client = false
Expand Down Expand Up @@ -344,7 +344,7 @@ def test_list_must_not_display_sources_from_pyproject_toml(
installer.only-binary = null
installer.parallel = true
installer.re-resolve = true
keyring.enabled = true
keyring.enabled = false
repositories.foo.url = "https://foo.bar/simple/"
requests.max-retries = 0
solver.lazy-wheel = true
Expand Down
6 changes: 6 additions & 0 deletions tests/utils/test_authenticator.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ def repo() -> dict[str, dict[str, str]]:
return {"foo": {"url": "https://foo.bar/simple/"}}


@pytest.fixture
def config(config: Config) -> Config:
config.config["keyring"]["enabled"] = True
return config


@pytest.fixture
def mock_config(config: Config, repo: dict[str, dict[str, str]]) -> Config:
config.merge(
Expand Down
20 changes: 17 additions & 3 deletions tests/utils/test_password_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@
from tests.conftest import DummyBackend


@pytest.fixture
def config(config: Config) -> Config:
config.config["keyring"]["enabled"] = True
return config


def test_set_http_password(
config: Config, with_simple_keyring: None, dummy_keyring: DummyBackend
) -> None:
Expand Down Expand Up @@ -368,10 +374,18 @@ def test_get_pypi_token_with_env_var_not_available(
assert result_token is None


def test_disabled_keyring_never_called(
config: Config, with_simple_keyring: None, dummy_keyring: DummyBackend
@pytest.mark.parametrize("enabled", [False, True])
def test_disabled_or_unavailable_keyring_never_called(
config: Config,
with_simple_keyring: None,
dummy_keyring: DummyBackend,
mocker: MockerFixture,
enabled: bool,
) -> None:
config.config["keyring"]["enabled"] = False
if enabled:
# enabled but not available
mocker.patch.dict("sys.modules", {"keyring": None})
config.config["keyring"]["enabled"] = enabled
config.config["http-basic"] = {"onlyuser": {"username": "user"}}

manager = PasswordManager(config)
Expand Down

0 comments on commit 16a6c18

Please sign in to comment.