Files
hermes-caldav-mcp/tests/keyring_fakes.py
xeno 91823006e2
Some checks failed
Offline checks / test (3.11) (push) Has been cancelled
Offline checks / test (3.13) (push) Has been cancelled
feat: publish lightweight Hermes Nextcloud calendar MCP
2026-09-30 22:53:46 +03:00

113 lines
4.2 KiB
Python

"""Synthetic D-Bus boundary; real SecretStorage negotiates and decrypts sessions."""
import json
from dataclasses import asdict
from jeepney import HeaderFields, new_error, new_method_return
from secretstorage.dhcrypto import Session
from secretstorage.util import format_secret
class KeyringBus:
def __init__(self, profile=None):
self.names = {
"org.gnome.keyring": ":1.10",
"org.kde.secretservicecompat": ":1.20",
"org.freedesktop.secrets": ":1.10",
}
self.activatable = set(self.names)
self.locked = set()
self.plain = set()
self.missing_default = set()
self.calls = []
self.sessions = {}
self.closed = False
self.switch_default_on_read = False
self.payload = (
json.dumps({"version": 1, "profile": asdict(profile)}).encode()
if profile
else None
)
def close(self):
self.closed = True
def send_and_get_reply(self, message, *, timeout=None):
fields = message.header.fields
destination = fields[HeaderFields.destination]
method = fields[HeaderFields.member]
self.calls.append((destination, method, message.body, timeout))
def reply(signature, *body):
return new_method_return(message, signature, body)
def error(name):
return new_error(message, name, "s", ("synthetic error",))
if destination == "org.freedesktop.DBus":
if method == "ListNames":
return reply("as", list(self.names))
if method == "ListActivatableNames":
return reply("as", list(self.activatable))
if method == "GetNameOwner":
if message.body[0] not in self.names:
return error("org.freedesktop.DBus.Error.NameHasNoOwner")
return reply("s", self.names[message.body[0]])
if method == "GetConnectionUnixProcessID":
return reply("u", 123)
owner = self.names.get(destination, destination)
if owner not in {":1.10", ":1.20", ":1.30"}:
return error("org.freedesktop.DBus.Error.ServiceUnknown")
if method == "ReadAlias":
if self.switch_default_on_read:
self.names["org.freedesktop.secrets"] = ":1.20"
return reply(
"o",
"/"
if owner in self.missing_default
else "/org/freedesktop/secrets/collection/default",
)
if method == "Get":
name = message.body[1]
values = {
"Label": ("s", "Synthetic wallet"),
"Locked": ("b", owner in self.locked),
"Attributes": (
"a{ss}",
{"application": "hermes-caldav-mcp", "profile": "default"},
),
}
return reply("v", values[name])
if method == "OpenSession":
if owner in self.plain:
if message.body[0] != "plain":
return error("org.freedesktop.DBus.Error.NotSupported")
return reply("vo", ("s", ""), "/org/freedesktop/secrets/session/plain")
session = Session()
session.object_path = "/org/freedesktop/secrets/session/encrypted"
session.set_server_public_key(int.from_bytes(message.body[1][1], "big"))
self.sessions[owner] = session
return reply(
"vo",
("ay", session.my_public_key.to_bytes(128, "big")),
session.object_path,
)
if method == "SearchItems":
return reply(
"ao",
["/org/freedesktop/secrets/collection/default/item"]
if self.payload
else [],
)
if method == "GetSecret":
return reply(
"(oayays)",
format_secret(self.sessions[owner], self.payload, "application/json"),
)
if method == "CreateItem":
return reply("oo", "/org/freedesktop/secrets/collection/default/item", "/")
if method == "Delete":
self.payload = None
return reply("o", "/")
raise AssertionError("Unexpected D-Bus method: " + method)