Repository navigation
Conversation
The class raises on every operation by aliasing them to get_password, but get_credential was not in the alias list, so it used the base implementation — whose `if username is not None:` guard returns None before ever calling get_password. A caller asking for a credential with no username therefore got "no such credential" on a machine that has no keyring backend at all. The base class keeps its None-on-omitted-username behavior, which keyring/testing/backend.py::test_credential asserts for every real backend, so the alias is the only correct site.
8787eb6 to
02e3b14
Compare
|
Hi @jaraco — still 3 files (+19/−1) at 02e3b14, and I have not pushed anything since.
One thing I would like your call on: I added |
|
Following up on the two options from my Oct 10 note (still exactly 3 files, +19/−1, at |
keyring.backends.fail.Keyringis documented as "Keyring that raises error on every operation", and it expresses that by aliasing the operations to the one that raises:get_credentialwas not in that list, so it used the base implementation, whose guard returnsNonebefore it ever reachesget_password:So on a machine with no usable backend:
get_password('svc', 'user')NoKeyringErrorset_password('svc', 'user', 'pw')NoKeyringErrordelete_password('svc', 'user')NoKeyringErrorget_credential('svc', 'user')NoKeyringErrorget_credential('svc', None)NoneThat last row is the problem: a caller asking for a credential without a username is told "no such credential", when the truth is "there is no keyring at all".
keyring get --mode creds svcexits 1 with empty stdout and no diagnostic, which is the shape of #711's "nothing is returned and a non-zero return code".The fix is one token on the existing alias line:
The alias is the only correct site. The base class must keep returning
Nonefor an omitted username — that is the documented contract for a backend that needs a username, andkeyring/testing/backend.py::test_credentialassertsget_credential('service', None) is Nonefor every real backend. Changing the base would break all of them.Two things I checked before proposing it. The fix goes silent→loud, not the other way round: nothing that used to return a value now returns a wrong value, it raises instead. And
fail.Keyringis never a chainer child, so no shared path changes:ChainerBackend.backendsfilters onkeyring.priority > 0andfail.priorityis0, so the chainer sees an empty child list.git logputs the alias line on7564a007("Fix typo in fail.Keyring method name", 2018-02-15), which correcteddelete_pasword→delete_passwordbut left the omission.get_credentialwas added to the base class later, inf32f39c1("Fix #350 Add get_credential() API", 2018-10-27) — after the alias line, which is how the gap opened. No commit ever added it to the alias.Tests
tests/backends/test_fail.py, parametrized over the username so the one-sided nature of the bug is pinned:On
7603e7cthe[None]case fails withFailed: DID NOT RAISE NoKeyringErrorand the['user']case already passes; both pass here.Validation:
pytest -qis 4 failed, 48 passed, 80 skipped against 4 failed, 46 passed, 80 skipped on7603e7c— the two new parametrizations and nothing else. The four failures are pre-existing environment gaps that fail identically on the base commit (keyring/core.py::keyring.core.disable,tests/test_multiprocess.py::test_multiprocess_get,::test_multiprocess_get_after_native_get,tests/test_packaging.py::test_entry_point).ruff checkandruff format --checkare clean on both files.One consequence worth your call
keyring get --mode creds svcon a backend-less machine will now print aNoKeyringErrortraceback where it previously exited 1 silently. That is more informative but uglier; catching it inkeyring/cli.pywould be the tidy version, but that path has been reshaped by #678 and I would rather not widen this diff. If you would prefer an explicitget_credentialmethod onfail.Keyringthat raises, rather than extending the alias, that is a one-line difference and I am happy to switch.