Files
mopidy-radionet/venv/lib/python3.7/site-packages/mopidy/config/keyring.py
2020-01-18 20:01:00 +01:00

178 lines
5.3 KiB
Python

import logging
logger = logging.getLogger(__name__)
try:
import dbus
except ImportError:
dbus = None
# XXX: Hack to workaround introspection bug caused by gnome-keyring, should be
# fixed by version 3.5 per:
# https://git.gnome.org/browse/gnome-keyring/commit/?id=5dccbe88eb94eea9934e2b7
if dbus:
EMPTY_STRING = dbus.String("", variant_level=1)
else:
EMPTY_STRING = ""
FETCH_ERROR = (
"Fetching passwords from your keyring failed. Any passwords "
"stored in the keyring will not be available."
)
def fetch():
if not dbus:
logger.debug("%s (dbus not installed)", FETCH_ERROR)
return []
try:
bus = dbus.SessionBus()
except dbus.exceptions.DBusException as e:
logger.debug("%s (%s)", FETCH_ERROR, e)
return []
if not bus.name_has_owner("org.freedesktop.secrets"):
logger.debug(
"%s (org.freedesktop.secrets service not running)", FETCH_ERROR
)
return []
service = _service(bus)
session = service.OpenSession("plain", EMPTY_STRING)[1]
items, locked = service.SearchItems({"service": "mopidy"})
if not locked and not items:
return []
if locked:
# There is a chance we can unlock without prompting the users...
items, prompt = service.Unlock(locked)
if prompt != "/":
_prompt(bus, prompt).Dismiss()
logger.debug("%s (Keyring is locked)", FETCH_ERROR)
return []
result = []
secrets = service.GetSecrets(items, session, byte_arrays=True)
for item_path, values in secrets.items():
session_path, parameters, value, content_type = values
attrs = _item_attributes(bus, item_path)
result.append((attrs["section"], attrs["key"], bytes(value)))
return result
def set(section, key, value):
"""Store a secret config value for a given section/key.
Indicates if storage failed or succeeded.
"""
if not dbus:
logger.debug(
"Saving %s/%s to keyring failed. (dbus not installed)", section, key
)
return False
try:
bus = dbus.SessionBus()
except dbus.exceptions.DBusException as e:
logger.debug("Saving %s/%s to keyring failed. (%s)", section, key, e)
return False
if not bus.name_has_owner("org.freedesktop.secrets"):
logger.debug(
"Saving %s/%s to keyring failed. "
"(org.freedesktop.secrets service not running)",
section,
key,
)
return False
service = _service(bus)
collection = _collection(bus)
if not collection:
return False
if isinstance(value, str):
value = value.encode()
session = service.OpenSession("plain", EMPTY_STRING)[1]
secret = dbus.Struct(
(session, "", dbus.ByteArray(value), "plain/text; charset=utf8")
)
label = f"mopidy: {section}/{key}"
attributes = {"service": "mopidy", "section": section, "key": key}
properties = {
"org.freedesktop.Secret.Item.Label": label,
"org.freedesktop.Secret.Item.Attributes": attributes,
}
try:
item, prompt = collection.CreateItem(properties, secret, True)
except dbus.exceptions.DBusException as e:
# TODO: catch IsLocked errors etc.
logger.debug("Saving %s/%s to keyring failed. (%s)", section, key, e)
return False
if prompt == "/":
return True
_prompt(bus, prompt).Dismiss()
logger.debug(
"Saving secret %s/%s failed. (Keyring is locked)", section, key
)
return False
def _service(bus):
return _interface(
bus, "/org/freedesktop/secrets", "org.freedesktop.Secret.Service"
)
# NOTE: depending on versions and setup 'default' might not exists, so try and
# use it but fall back to the 'login' collection, and finally the 'session' one
# if all else fails. We should probably create a keyring/collection setting
# that allows users to set this so they have control over where their secrets
# get stored.
def _collection(bus):
for name in "aliases/default", "collection/login", "collection/session":
path = "/org/freedesktop/secrets/" + name
if _collection_exists(bus, path):
break
else:
return None
return _interface(bus, path, "org.freedesktop.Secret.Collection")
# NOTE: Hack to probe if a given collection actually exists. Needed to work
# around an introspection bug in setting passwords for non-existant aliases.
def _collection_exists(bus, path):
try:
item = _interface(bus, path, "org.freedesktop.DBus.Properties")
item.Get("org.freedesktop.Secret.Collection", "Label")
return True
except dbus.exceptions.DBusException:
return False
# NOTE: We could call prompt.Prompt('') to unlock the keyring when it is not
# '/', but we would then also have to arrange to setup signals to wait until
# this has been completed. So for now we just dismiss the prompt and expect
# keyrings to be unlocked.
def _prompt(bus, path):
return _interface(bus, path, "Prompt")
def _item_attributes(bus, path):
item = _interface(bus, path, "org.freedesktop.DBus.Properties")
result = item.Get("org.freedesktop.Secret.Item", "Attributes")
return {bytes(k): bytes(v) for k, v in result.items()}
def _interface(bus, path, interface):
obj = bus.get_object("org.freedesktop.secrets", path)
return dbus.Interface(obj, interface)