Skip to content

Commit

Permalink
add config option keyring.enabled to allow disabling the keyring
Browse files Browse the repository at this point in the history
  • Loading branch information
Popkornium18 committed Feb 3, 2024
1 parent 753ab64 commit baa8ed2
Show file tree
Hide file tree
Showing 9 changed files with 88 additions and 1 deletion.
12 changes: 12 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -506,3 +506,15 @@ repository.
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`:

**Type**: `boolean`

**Default**: `true`

**Environment Variable**: `POETRY_KEYRING_ENABLED`

Enable the system keyring for storing credentials.
See [Repositories - Configuring credentials]({{< relref "repositories#configuring-credentials" >}})
for more information.
9 changes: 9 additions & 0 deletions docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,15 @@ required variables explicitly or `passenv = "*"` to forward all of them.
Linux systems may require forwarding the `DBUS_SESSION_BUS_ADDRESS` variable to allow access to the system keyring,
though this may vary between desktop environments.

Alternatively, you can disable the keyring completely:

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

Be aware that this will cause Poetry to write passwords to plaintext config files.
You will need to set the credentials again after changing this setting.

### Is Nox supported?

Use the [`nox-poetry`](https://github.com/cjolowicz/nox-poetry) package to install locked versions of
Expand Down
6 changes: 6 additions & 0 deletions docs/repositories.md
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,12 @@ If a system keyring is available and supported, the password is stored to and re

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:

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

{{% note %}}

Poetry will fallback to Pip style use of keyring so that backends like
Expand Down
4 changes: 4 additions & 0 deletions src/poetry/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ class Config:
"warnings": {
"export": True,
},
"keyring": {
"enabled": True,
},
}

def __init__(
Expand Down Expand Up @@ -301,6 +304,7 @@ def _get_normalizer(name: str) -> Callable[[str], Any]:
"installer.parallel",
"solver.lazy-wheel",
"warnings.export",
"keyring.enabled",
}:
return boolean_normalizer

Expand Down
1 change: 1 addition & 0 deletions src/poetry/console/commands/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ def unique_config_values(self) -> dict[str, tuple[Any, Any]]:
),
"solver.lazy-wheel": (boolean_validator, boolean_normalizer),
"warnings.export": (boolean_validator, boolean_normalizer),
"keyring.enabled": (boolean_validator, boolean_normalizer),
}

return unique_config_values
Expand Down
2 changes: 1 addition & 1 deletion src/poetry/utils/password_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def __init__(self, config: Config) -> None:

@functools.cached_property
def use_keyring(self) -> bool:
return PoetryKeyring.is_available()
return self._config.get("keyring.enabled") and PoetryKeyring.is_available()

@functools.cached_property
def keyring(self) -> PoetryKeyring:
Expand Down
20 changes: 20 additions & 0 deletions tests/config/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,17 @@
from poetry.config.config import Config
from poetry.config.config import boolean_normalizer
from poetry.config.config import int_normalizer
from poetry.utils.password_manager import PasswordManager
from poetry.utils.password_manager import PoetryKeyringError
from tests.helpers import flatten_dict


if TYPE_CHECKING:
from collections.abc import Callable
from collections.abc import Iterator

from tests.conftest import DummyBackend

Normalizer = Callable[[str], Any]


Expand Down Expand Up @@ -81,3 +85,19 @@ def test_config_expands_tilde_for_virtualenvs_path(
) -> None:
config.merge({"virtualenvs": {"path": path_config}})
assert config.virtualenvs_path == expected


def test_disabled_keyring_is_unavailable(
config: Config, with_simple_keyring: None, dummy_keyring: DummyBackend
) -> None:
manager = PasswordManager(config)
assert manager.use_keyring

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

with pytest.raises(PoetryKeyringError) as e:
_ = manager.keyring

assert str(e.value) == "Access to keyring was requested, but it is not available"
6 changes: 6 additions & 0 deletions tests/console/commands/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ def test_list_displays_default_value_if_not_set(
installer.modern-installation = true
installer.no-binary = null
installer.parallel = true
keyring.enabled = true
solver.lazy-wheel = true
virtualenvs.create = true
virtualenvs.in-project = null
Expand Down Expand Up @@ -90,6 +91,7 @@ def test_list_displays_set_get_setting(
installer.modern-installation = true
installer.no-binary = null
installer.parallel = true
keyring.enabled = true
solver.lazy-wheel = true
virtualenvs.create = false
virtualenvs.in-project = null
Expand Down Expand Up @@ -142,6 +144,7 @@ def test_unset_setting(
installer.modern-installation = true
installer.no-binary = null
installer.parallel = true
keyring.enabled = true
solver.lazy-wheel = true
virtualenvs.create = true
virtualenvs.in-project = null
Expand Down Expand Up @@ -172,6 +175,7 @@ def test_unset_repo_setting(
installer.modern-installation = true
installer.no-binary = null
installer.parallel = true
keyring.enabled = true
solver.lazy-wheel = true
virtualenvs.create = true
virtualenvs.in-project = null
Expand Down Expand Up @@ -300,6 +304,7 @@ def test_list_displays_set_get_local_setting(
installer.modern-installation = true
installer.no-binary = null
installer.parallel = true
keyring.enabled = true
solver.lazy-wheel = true
virtualenvs.create = false
virtualenvs.in-project = null
Expand Down Expand Up @@ -338,6 +343,7 @@ def test_list_must_not_display_sources_from_pyproject_toml(
installer.modern-installation = true
installer.no-binary = null
installer.parallel = true
keyring.enabled = true
repositories.foo.url = "https://foo.bar/simple/"
solver.lazy-wheel = true
virtualenvs.create = true
Expand Down
29 changes: 29 additions & 0 deletions tests/utils/test_password_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,3 +330,32 @@ def test_get_pypi_token_with_env_var_not_available(
result_token = manager.get_pypi_token(repo_name)

assert result_token is None


def test_disabled_keyring_never_called(
config: Config, with_simple_keyring: None, dummy_keyring: DummyBackend
) -> None:
config.config["keyring"]["enabled"] = False
config.config["http-basic"] = {"onlyuser": {"username": "user"}}

manager = PasswordManager(config)
num_public_functions = len([f for f in dir(manager) if not f.startswith("_")])
if num_public_functions != 10:
pytest.fail(
f"A function was added to or removed from the {PasswordManager.__name__} "
"class without reflecting this change in this test."
)

manager.set_pypi_token(repo_name="exists", token="token")
manager.get_pypi_token(repo_name="exists")
manager.get_pypi_token(repo_name="doesn't exist")
manager.delete_pypi_token(repo_name="exists")
manager.delete_pypi_token(repo_name="doesn't exist")
manager.set_http_password(repo_name="exists", username="user", password="password")
manager.get_http_auth(repo_name="exists")
manager.get_http_auth(repo_name="doesn't exist")
manager.get_http_auth(repo_name="onlyuser")
manager.delete_http_password(repo_name="exits")
manager.delete_http_password(repo_name="doesn't exist")
manager.delete_http_password(repo_name="onlyuser")
manager.get_credential("a", "b", "c", username="user")

0 comments on commit baa8ed2

Please sign in to comment.