Request: API to query if password exists without triggering Wallet creation and its UI
This is a request to fix an issue in Keyring, the most widely used python keyring library, and perhaps other similar libraries/clients.
Consider the following scenario, as described in this bug report:
- Gnome Shell user, KWallet was pulled as dependency of some installed KDE utility but never actually used.
- Keyring, when asked for a (non-existing) password, looks for it in every registered backend, including KWallet.
- Some DBus calls are made to query if a password exists in KWallet:
- opening
org.kde.kwalletd5/modules/kwalletd5.networkWallet()
DBus interface -
.hasEntry()
to check if password exists (in this example, it doesn't) - if hasEntry succeeds,
.readPassword()
to finally get the data.
- opening
Since no Wallet exists, KWalled pops up its Wallet Creation GUI! A very undesirable side-effect when merely querying for password existence. After all, if there is no Wallet then .hasEntry()
could (should?) have short-circuited to say no.
Either approach could solve that problem:
-
.networkWallet()
/.hasEntry()
could not trigger Wallet creation, either by default (preferable) or by acreate=False
flag parameter, that could default toTrue
to preserve backward-compatibility with current behavior. - A method/interface that allow clients to probe if Wallet infrastructure exists prior to issuing
.networkWallet()
/.hasEntry()
. Is there any such method already?
Relevant (python) code that trigger the undesirable wallet creation GUI, if that helps:
def connected(self, service):
if self.handle >= 0:
if self.iface.isOpen(self.handle):
return True
bus = dbus.SessionBus(mainloop=DBusGMainLoop())
wId = 0
try:
remote_obj = bus.get_object(self.bus_name, self.object_path)
self.iface = dbus.Interface(remote_obj, 'org.kde.KWallet')
self.handle = self.iface.open(self.iface.networkWallet(), wId, self.appid)
except dbus.DBusException as e:
raise InitError('Failed to open keyring: %s.' % e)
if self.handle < 0:
return False
self._migrate(service)
return True
def get_password(self, service, username):
"""Get password of the username for the service"""
if not self.connected(service):
# the user pressed "cancel" when prompted to unlock their keyring.
raise KeyringLocked("Failed to unlock the keyring!")
if not self.iface.hasEntry(self.handle, service, username, self.appid):
return None
password = self.iface.readPassword(self.handle, service, username, self.appid)
return str(password)