Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions keyring/backends/chainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from .. import backend
from ..compat import properties
from ..errors import PasswordDeleteError
from . import fail


Expand Down Expand Up @@ -63,6 +64,9 @@ def delete_password(self, service, username):
return keyring.delete_password(service, username)
except NotImplementedError:
pass
except PasswordDeleteError:
if keyring == self.backends[-1]:
raise

def get_credential(self, service, username):
for keyring in self.backends:
Expand Down
81 changes: 81 additions & 0 deletions tests/backends/test_chainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import keyring.backends.chainer
from keyring import backend
from keyring.errors import PasswordDeleteError


@pytest.fixture
Expand Down Expand Up @@ -30,6 +31,52 @@ def set_password(self, system, user, password):
monkeypatch.setattr('keyring.backend.get_all_keyring', get_two)


@pytest.fixture
def delete_test_keyrings(monkeypatch):
"""
Fixture that creates backends where passwords exist in lower priority backend.
"""
class HighPriorityKeyring(backend.KeyringBackend):
priority = 2
storage = {}

def get_password(self, system, user):
return self.storage.get((system, user))

def set_password(self, system, user, password):
self.storage[(system, user)] = password

def delete_password(self, system, user):
key = (system, user)
if key in self.storage:
del self.storage[key]
else:
raise PasswordDeleteError("Password not found")

class LowPriorityKeyring(backend.KeyringBackend):
priority = 1
storage = {('test', 'user'): 'old-password'}

def get_password(self, system, user):
return self.storage.get((system, user))

def set_password(self, system, user, password):
self.storage[(system, user)] = password

def delete_password(self, system, user):
key = (system, user)
if key in self.storage:
del self.storage[key]
else:
raise PasswordDeleteError("Password not found")

high = HighPriorityKeyring()
low = LowPriorityKeyring()

monkeypatch.setattr('keyring.backend.get_all_keyring', lambda: [high, low])
return high, low


class TestChainer:
def test_chainer_gets_from_highest_priority(self, two_keyrings):
chainer = keyring.backends.chainer.ChainerBackend()
Expand All @@ -45,3 +92,37 @@ def test_chainer_defers_to_fail(self, monkeypatch):
assert keyring.backend.by_priority(
keyring.backends.chainer.ChainerBackend
) < keyring.backend.by_priority(keyring.backends.fail.Keyring)

def test_delete_password_tries_all_backends(self, delete_test_keyrings):
"""
Test that delete_password tries all backends in the chain,
not just the highest priority one. Addresses issue #697.
"""
high, low = delete_test_keyrings
chainer = keyring.backends.chainer.ChainerBackend()

# Verify the password exists in the low priority backend
assert low.get_password('test', 'user') == 'old-password'
# Verify it doesn't exist in high priority backend
assert high.get_password('test', 'user') is None
# Verify chainer can retrieve it
assert chainer.get_password('test', 'user') == 'old-password'

# Now delete it via chainer - should succeed even though high priority backend fails
chainer.delete_password('test', 'user')

# Verify it's deleted from low priority backend
assert low.get_password('test', 'user') is None
# Verify chainer can't retrieve it anymore
assert chainer.get_password('test', 'user') is None

def test_delete_password_raises_if_all_backends_fail(self, delete_test_keyrings):
"""
Test that delete_password raises PasswordDeleteError if all backends fail.
"""
high, low = delete_test_keyrings
chainer = keyring.backends.chainer.ChainerBackend()

# Try to delete a password that doesn't exist anywhere
with pytest.raises(PasswordDeleteError):
chainer.delete_password('nonexistent', 'user')
Loading