diff --git a/pantalaimon/client.py b/pantalaimon/client.py index a0be504..b8e6fc7 100644 --- a/pantalaimon/client.py +++ b/pantalaimon/client.py @@ -9,6 +9,7 @@ from nio import (AsyncClient, ClientConfig, EncryptionError, from nio.store import SqliteStore from pantalaimon.log import logger +from pantalaimon.ui import DevicesMessage class PanClient(AsyncClient): @@ -17,6 +18,7 @@ class PanClient(AsyncClient): def __init__( self, homeserver, + queue=None, user="", device_id="", store_path="", @@ -29,6 +31,7 @@ class PanClient(AsyncClient): ssl, proxy) self.task = None + self.queue = queue self.loop_stopped = asyncio.Event() self.synced = asyncio.Event() @@ -78,6 +81,12 @@ class PanClient(AsyncClient): key_query_response = await self.keys_query() if isinstance(key_query_response, KeysQueryResponse): self.verify_devices(key_query_response.changed) + message = DevicesMessage( + self.user_id, + self.device_id, + key_query_response.changed + ) + await self.queue.put(message) if not isinstance(response, SyncResponse): # TODO error handling diff --git a/pantalaimon/daemon.py b/pantalaimon/daemon.py index 7c04b54..9916157 100755 --- a/pantalaimon/daemon.py +++ b/pantalaimon/daemon.py @@ -4,27 +4,20 @@ import asyncio import json import os import sys -from enum import Enum, auto from functools import partial from ipaddress import ip_address from json import JSONDecodeError -from queue import Empty from urllib.parse import urlparse import aiohttp import attr import click -import dbus -import dbus.exceptions -import dbus.service import janus import keyring import logbook from aiohttp import ClientSession, web from aiohttp.client_exceptions import ContentTypeError from appdirs import user_data_dir -from dbus.mainloop.glib import DBusGMainLoop -from gi.repository import GLib from logbook import StderrHandler from multidict import CIMultiDict from nio import EncryptionError, GroupEncryptionError, LoginResponse @@ -32,69 +25,7 @@ from nio import EncryptionError, GroupEncryptionError, LoginResponse from pantalaimon.client import PanClient from pantalaimon.log import logger from pantalaimon.store import ClientInfo, PanStore - - -class Tasks(Enum): - shutdown = auto() - - -class Devices(dbus.service.Object): - def __init__(self, bus_name, device_list): - super().__init__(bus_name, "/org/pantalaimon/Devices") - self.device_list = device_list - - @dbus.service.method("org.pantalaimon.devices.list", - out_signature="a{sa{saa{ss}}}") - def list(self): - return self.device_list - - -class Users(dbus.service.Object): - def __init__(self, bus_name, user_list=None): - super().__init__(bus_name, "/org/pantalaimon/Users") - self.users = user_list - - @dbus.service.method("org.pantalaimon.users.list", - out_signature="a(ss)") - def list(self): - return self.users - - -def dbus_loop(task_queue, data_dir): - DBusGMainLoop(set_as_default=True) - loop = GLib.MainLoop() - - bus_name = dbus.service.BusName("org.pantalaimon", - bus=dbus.SessionBus(), - do_not_queue=True) - - store = PanStore(data_dir) - users = store.load_all_users() - devices = store.load_all_devices() - - # TODO update bus data if the asyncio thread tells us so. - Users(bus_name, users) - Devices(bus_name, devices) - - def task_callback(): - try: - task = task_queue.get_nowait() - except Empty: - return True - - if task == Tasks.shutdown: - task_queue.task_done() - loop.quit() - return False - - GLib.timeout_add(100, task_callback) - - loop.run() - - -async def shutdown_dbus(future, queue, app): - await queue.put(Tasks.shutdown) - await future +from pantalaimon.ui import glib_loop, shutdown_glib_loop @attr.s @@ -139,6 +70,7 @@ class ProxyDaemon: pan_client = PanClient( self.homeserver_url, + self.queue, user_id, device_id, store_path=self.data_dir, @@ -248,6 +180,7 @@ class ProxyDaemon: pan_client = PanClient( self.homeserver_url, + self.queue, user, store_path=self.data_dir, ssl=self.ssl, @@ -716,12 +649,12 @@ def start( )) data_dir = user_data_dir("pantalaimon", "") - fut = loop.run_in_executor(None, dbus_loop, queue.sync_q, data_dir) + fut = loop.run_in_executor(None, glib_loop, queue.sync_q, data_dir) - kill_dbus_loop = partial(shutdown_dbus, fut, queue.async_q) + kill_glib = partial(shutdown_glib_loop, fut, queue.async_q) app.on_shutdown.append(proxy.shutdown) - app.on_shutdown.append(kill_dbus_loop) + app.on_shutdown.append(kill_glib) web.run_app(app, host=str(listen_address), port=listen_port) diff --git a/pantalaimon/store.py b/pantalaimon/store.py index 5d44e50..4ff6eb8 100644 --- a/pantalaimon/store.py +++ b/pantalaimon/store.py @@ -223,6 +223,9 @@ class PanStore: for d in account.device_keys: + if d.deleted: + continue + try: trust_state = d.trust_state[0].state except IndexError: diff --git a/pantalaimon/ui.py b/pantalaimon/ui.py new file mode 100644 index 0000000..847136f --- /dev/null +++ b/pantalaimon/ui.py @@ -0,0 +1,152 @@ +import attr + +import dbus +import dbus.exceptions +import dbus.service + +from dbus.mainloop.glib import DBusGMainLoop +from gi.repository import GLib + +from queue import Empty + +from nio.store import TrustState +from pantalaimon.store import PanStore +from pantalaimon.log import logger + + +@attr.s +class Message: + pass + + +@attr.s +class ShutDownMessage(Message): + pass + + +@attr.s +class DevicesMessage(Message): + user_id = attr.ib() + device_id = attr.ib() + devices = attr.ib() + + +@attr.s +class DeviceVerifyMessage(Message): + user_id = attr.ib() + device_id = attr.ib() + device_user = attr.ib() + device_device_id = attr.ib() + + +class Devices(dbus.service.Object): + def __init__(self, bus_name, device_list): + super().__init__(bus_name, "/org/pantalaimon/Devices") + self.device_list = device_list + + @dbus.service.method("org.pantalaimon.devices.list", + out_signature="a{sa{saa{ss}}}") + def list(self): + return self.device_list + + @dbus.service.method("org.pantalaimon.devices.verify", + in_signature="ssss") + def verify(self, user_id, device_id, devices_user, devices_id): + device_store = self.device_list[user_id].get(device_id, None) + + if not device_store: + logger.debug(f"Not verifying device, no store found for user " + f"{user_id}") + return + + logger.debug(f"Verifying device {devices_user} {devices_id}") + return + + @dbus.service.method("org.pantalaimon.devices.start_verification", + in_signature="ssss") + def start_verify(self, user_id, device_id, devices_user, devices_id): + device_store = self.device_list[user_id].get(device_id, None) + + if not device_store: + logger.info(f"Not verifying device, no store found for user " + f"{user_id}") + return + + logger.info(f"Verifying device {devices_user} {devices_id}") + return + + def update_devices(self, message): + device_store = self.device_list[message.user_id][message.device_id] + + for user_id, device_dict in message.devices.items(): + for device in device_dict.values(): + if device.deleted: + device_store[user_id].pop(device.id, None) + else: + device_store[user_id][device.id] = { + "user_id": device.user_id, + "device_id": device.id, + "fingerprint_key": device.ed25519, + "sender_key": device.curve25519, + "trust_state": TrustState.unset.name, + } + + +class Users(dbus.service.Object): + def __init__(self, bus_name, user_list=None): + super().__init__(bus_name, "/org/pantalaimon/Control") + self.users = user_list + + @dbus.service.method("org.pantalaimon.control.list_users", + out_signature="a(ss)") + def list(self): + return self.users + + @dbus.service.method("org.pantalaimon.control.export_keys", + in_signature="ss") + def export_keys(self, user, filepath): + return + + +def glib_loop(queue, data_dir): + DBusGMainLoop(set_as_default=True) + loop = GLib.MainLoop() + + bus_name = dbus.service.BusName("org.pantalaimon", + bus=dbus.SessionBus(), + do_not_queue=True) + + store = PanStore(data_dir) + users = store.load_all_users() + devices = store.load_all_devices() + + # TODO update bus data if the asyncio thread tells us so. + Users(bus_name, users) + device_bus = Devices(bus_name, devices) + + def message_callback(): + try: + message = queue.get_nowait() + except Empty: + return True + + logger.info(f"Dbus loop received message {message}") + + if isinstance(message, ShutDownMessage): + queue.task_done() + loop.quit() + return False + + elif isinstance(message, DevicesMessage): + device_bus.update_devices(message) + + return True + + GLib.timeout_add(100, message_callback) + + loop.run() + + +async def shutdown_glib_loop(future, queue, app): + await queue.put(ShutDownMessage()) + await future