Skip to content

Commit baa8ed2

Browse files
committed
add config option keyring.enabled to allow disabling the keyring
1 parent 753ab64 commit baa8ed2

File tree

9 files changed

+88
-1
lines changed

9 files changed

+88
-1
lines changed

docs/configuration.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,3 +506,15 @@ repository.
506506
Set client certificate for repository `<name>`.
507507
See [Repositories - Configuring credentials - Custom certificate authority]({{< relref "repositories#custom-certificate-authority-and-mutual-tls-authentication" >}})
508508
for more information.
509+
510+
### `keyring.enabled`:
511+
512+
**Type**: `boolean`
513+
514+
**Default**: `true`
515+
516+
**Environment Variable**: `POETRY_KEYRING_ENABLED`
517+
518+
Enable the system keyring for storing credentials.
519+
See [Repositories - Configuring credentials]({{< relref "repositories#configuring-credentials" >}})
520+
for more information.

docs/faq.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,15 @@ required variables explicitly or `passenv = "*"` to forward all of them.
154154
Linux systems may require forwarding the `DBUS_SESSION_BUS_ADDRESS` variable to allow access to the system keyring,
155155
though this may vary between desktop environments.
156156

157+
Alternatively, you can disable the keyring completely:
158+
159+
```bash
160+
poetry config keyring.enabled false
161+
```
162+
163+
Be aware that this will cause Poetry to write passwords to plaintext config files.
164+
You will need to set the credentials again after changing this setting.
165+
157166
### Is Nox supported?
158167

159168
Use the [`nox-poetry`](https://github.com/cjolowicz/nox-poetry) package to install locked versions of

docs/repositories.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,12 @@ If a system keyring is available and supported, the password is stored to and re
500500

501501
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).
502502

503+
If you do not want to use the keyring, you can tell Poetry to disable it and store the credentials in plaintext config files:
504+
505+
```bash
506+
poetry config keyring.enabled false
507+
```
508+
503509
{{% note %}}
504510

505511
Poetry will fallback to Pip style use of keyring so that backends like

src/poetry/config/config.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,9 @@ class Config:
137137
"warnings": {
138138
"export": True,
139139
},
140+
"keyring": {
141+
"enabled": True,
142+
},
140143
}
141144

142145
def __init__(
@@ -301,6 +304,7 @@ def _get_normalizer(name: str) -> Callable[[str], Any]:
301304
"installer.parallel",
302305
"solver.lazy-wheel",
303306
"warnings.export",
307+
"keyring.enabled",
304308
}:
305309
return boolean_normalizer
306310

src/poetry/console/commands/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ def unique_config_values(self) -> dict[str, tuple[Any, Any]]:
7979
),
8080
"solver.lazy-wheel": (boolean_validator, boolean_normalizer),
8181
"warnings.export": (boolean_validator, boolean_normalizer),
82+
"keyring.enabled": (boolean_validator, boolean_normalizer),
8283
}
8384

8485
return unique_config_values

src/poetry/utils/password_manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ def __init__(self, config: Config) -> None:
145145

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

150150
@functools.cached_property
151151
def keyring(self) -> PoetryKeyring:

tests/config/test_config.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,17 @@
1212
from poetry.config.config import Config
1313
from poetry.config.config import boolean_normalizer
1414
from poetry.config.config import int_normalizer
15+
from poetry.utils.password_manager import PasswordManager
16+
from poetry.utils.password_manager import PoetryKeyringError
1517
from tests.helpers import flatten_dict
1618

1719

1820
if TYPE_CHECKING:
1921
from collections.abc import Callable
2022
from collections.abc import Iterator
2123

24+
from tests.conftest import DummyBackend
25+
2226
Normalizer = Callable[[str], Any]
2327

2428

@@ -81,3 +85,19 @@ def test_config_expands_tilde_for_virtualenvs_path(
8185
) -> None:
8286
config.merge({"virtualenvs": {"path": path_config}})
8387
assert config.virtualenvs_path == expected
88+
89+
90+
def test_disabled_keyring_is_unavailable(
91+
config: Config, with_simple_keyring: None, dummy_keyring: DummyBackend
92+
) -> None:
93+
manager = PasswordManager(config)
94+
assert manager.use_keyring
95+
96+
config.config["keyring"]["enabled"] = False
97+
manager = PasswordManager(config)
98+
assert not manager.use_keyring
99+
100+
with pytest.raises(PoetryKeyringError) as e:
101+
_ = manager.keyring
102+
103+
assert str(e.value) == "Access to keyring was requested, but it is not available"

tests/console/commands/test_config.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ def test_list_displays_default_value_if_not_set(
5959
installer.modern-installation = true
6060
installer.no-binary = null
6161
installer.parallel = true
62+
keyring.enabled = true
6263
solver.lazy-wheel = true
6364
virtualenvs.create = true
6465
virtualenvs.in-project = null
@@ -90,6 +91,7 @@ def test_list_displays_set_get_setting(
9091
installer.modern-installation = true
9192
installer.no-binary = null
9293
installer.parallel = true
94+
keyring.enabled = true
9395
solver.lazy-wheel = true
9496
virtualenvs.create = false
9597
virtualenvs.in-project = null
@@ -142,6 +144,7 @@ def test_unset_setting(
142144
installer.modern-installation = true
143145
installer.no-binary = null
144146
installer.parallel = true
147+
keyring.enabled = true
145148
solver.lazy-wheel = true
146149
virtualenvs.create = true
147150
virtualenvs.in-project = null
@@ -172,6 +175,7 @@ def test_unset_repo_setting(
172175
installer.modern-installation = true
173176
installer.no-binary = null
174177
installer.parallel = true
178+
keyring.enabled = true
175179
solver.lazy-wheel = true
176180
virtualenvs.create = true
177181
virtualenvs.in-project = null
@@ -300,6 +304,7 @@ def test_list_displays_set_get_local_setting(
300304
installer.modern-installation = true
301305
installer.no-binary = null
302306
installer.parallel = true
307+
keyring.enabled = true
303308
solver.lazy-wheel = true
304309
virtualenvs.create = false
305310
virtualenvs.in-project = null
@@ -338,6 +343,7 @@ def test_list_must_not_display_sources_from_pyproject_toml(
338343
installer.modern-installation = true
339344
installer.no-binary = null
340345
installer.parallel = true
346+
keyring.enabled = true
341347
repositories.foo.url = "https://foo.bar/simple/"
342348
solver.lazy-wheel = true
343349
virtualenvs.create = true

tests/utils/test_password_manager.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,3 +330,32 @@ def test_get_pypi_token_with_env_var_not_available(
330330
result_token = manager.get_pypi_token(repo_name)
331331

332332
assert result_token is None
333+
334+
335+
def test_disabled_keyring_never_called(
336+
config: Config, with_simple_keyring: None, dummy_keyring: DummyBackend
337+
) -> None:
338+
config.config["keyring"]["enabled"] = False
339+
config.config["http-basic"] = {"onlyuser": {"username": "user"}}
340+
341+
manager = PasswordManager(config)
342+
num_public_functions = len([f for f in dir(manager) if not f.startswith("_")])
343+
if num_public_functions != 10:
344+
pytest.fail(
345+
f"A function was added to or removed from the {PasswordManager.__name__} "
346+
"class without reflecting this change in this test."
347+
)
348+
349+
manager.set_pypi_token(repo_name="exists", token="token")
350+
manager.get_pypi_token(repo_name="exists")
351+
manager.get_pypi_token(repo_name="doesn't exist")
352+
manager.delete_pypi_token(repo_name="exists")
353+
manager.delete_pypi_token(repo_name="doesn't exist")
354+
manager.set_http_password(repo_name="exists", username="user", password="password")
355+
manager.get_http_auth(repo_name="exists")
356+
manager.get_http_auth(repo_name="doesn't exist")
357+
manager.get_http_auth(repo_name="onlyuser")
358+
manager.delete_http_password(repo_name="exits")
359+
manager.delete_http_password(repo_name="doesn't exist")
360+
manager.delete_http_password(repo_name="onlyuser")
361+
manager.get_credential("a", "b", "c", username="user")

0 commit comments

Comments
 (0)