daemon: Expose daemon users and user devices over a dbus interface.

This commit is contained in:
Damir Jelić 2019-04-17 13:32:32 +02:00
parent 42fc14ee35
commit 7038462e01
3 changed files with 145 additions and 5 deletions

View File

@ -4,18 +4,27 @@ import asyncio
import json import json
import os import os
import sys import sys
from enum import Enum, auto
from functools import partial
from ipaddress import ip_address from ipaddress import ip_address
from json import JSONDecodeError from json import JSONDecodeError
from queue import Empty
from urllib.parse import urlparse from urllib.parse import urlparse
import aiohttp import aiohttp
import attr import attr
import click import click
import dbus
import dbus.exceptions
import dbus.service
import janus
import keyring import keyring
import logbook import logbook
from aiohttp import ClientSession, web from aiohttp import ClientSession, web
from aiohttp.client_exceptions import ContentTypeError from aiohttp.client_exceptions import ContentTypeError
from appdirs import user_data_dir from appdirs import user_data_dir
from dbus.mainloop.glib import DBusGMainLoop
from gi.repository import GLib
from logbook import StderrHandler from logbook import StderrHandler
from multidict import CIMultiDict from multidict import CIMultiDict
from nio import EncryptionError, GroupEncryptionError, LoginResponse from nio import EncryptionError, GroupEncryptionError, LoginResponse
@ -25,10 +34,74 @@ from pantalaimon.log import logger
from pantalaimon.store import ClientInfo, PanStore 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
@attr.s @attr.s
class ProxyDaemon: class ProxyDaemon:
homeserver = attr.ib() homeserver = attr.ib()
data_dir = attr.ib() data_dir = attr.ib()
queue = attr.ib()
proxy = attr.ib(default=None) proxy = attr.ib(default=None)
ssl = attr.ib(default=None) ssl = attr.ib(default=None)
@ -420,7 +493,7 @@ class ProxyDaemon:
self.default_session = None self.default_session = None
async def init(homeserver, http_proxy, ssl): async def init(homeserver, http_proxy, ssl, queue):
"""Initialize the proxy and the http server.""" """Initialize the proxy and the http server."""
data_dir = user_data_dir("pantalaimon", "") data_dir = user_data_dir("pantalaimon", "")
@ -429,7 +502,13 @@ async def init(homeserver, http_proxy, ssl):
except OSError: except OSError:
pass pass
proxy = ProxyDaemon(homeserver, data_dir, proxy=http_proxy, ssl=ssl) proxy = ProxyDaemon(
homeserver,
data_dir,
queue=queue,
proxy=http_proxy,
ssl=ssl,
)
app = web.Application() app = web.Application()
app.add_routes([ app.add_routes([
@ -626,12 +705,24 @@ def start(
logger.level = logbook.DEBUG logger.level = logbook.DEBUG
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
queue = janus.Queue(loop=loop)
proxy, app = loop.run_until_complete(init( proxy, app = loop.run_until_complete(init(
homeserver, homeserver,
proxy.geturl() if proxy else None, proxy.geturl() if proxy else None,
ssl ssl,
queue.async_q
)) ))
data_dir = user_data_dir("pantalaimon", "")
fut = loop.run_in_executor(None, dbus_loop, queue.sync_q, data_dir)
kill_dbus_loop = partial(shutdown_dbus, fut, queue.async_q)
app.on_shutdown.append(proxy.shutdown)
app.on_shutdown.append(kill_dbus_loop)
web.run_app(app, host=str(listen_address), port=listen_port) web.run_app(app, host=str(listen_address), port=listen_port)

View File

@ -1,8 +1,10 @@
import os import os
from collections import defaultdict
from typing import Dict, List, Optional, Tuple from typing import Dict, List, Optional, Tuple
import attr import attr
from nio.store import Accounts, use_database from nio.store import (Accounts, DeviceKeys, DeviceTrustState, TrustState,
use_database)
from peewee import (SQL, DoesNotExist, ForeignKeyField, Model, SqliteDatabase, from peewee import (SQL, DoesNotExist, ForeignKeyField, Model, SqliteDatabase,
TextField) TextField)
@ -57,13 +59,30 @@ class ClientInfo:
access_token = attr.ib(type=str) access_token = attr.ib(type=str)
@attr.s
class OlmDevice:
user_id = attr.ib()
id = attr.ib()
fp_key = attr.ib()
sender_key = attr.ib()
trust_state = attr.ib()
@attr.s @attr.s
class PanStore: class PanStore:
store_path = attr.ib(type=str) store_path = attr.ib(type=str)
database_name = attr.ib(type=str, default="pan.db") database_name = attr.ib(type=str, default="pan.db")
database = attr.ib(type=SqliteDatabase, init=False) database = attr.ib(type=SqliteDatabase, init=False)
database_path = attr.ib(type=str, init=False) database_path = attr.ib(type=str, init=False)
models = [Accounts, AccessTokens, Clients, Servers, ServerUsers] models = [
Accounts,
AccessTokens,
Clients,
Servers,
ServerUsers,
DeviceKeys,
DeviceTrustState,
]
def __attrs_post_init__(self): def __attrs_post_init__(self):
self.database_path = os.path.join( self.database_path = os.path.join(
@ -191,3 +210,32 @@ class PanStore:
clients[c.token] = client clients[c.token] = client
return clients return clients
@use_database
def load_all_devices(self):
# type (str, str) -> Dict[str, Dict[str, DeviceStore]]
store = defaultdict(dict)
query = Accounts.select()
for account in query:
device_store = []
for d in account.device_keys:
try:
trust_state = d.trust_state[0].state
except IndexError:
trust_state = TrustState.unset
device_store.append({
"user_id": d.user_id,
"device_id": d.device_id,
"fingerprint_key": d.fp_key,
"sender_key": d.sender_key,
"trust_state": trust_state.name
})
store[account.user_id][account.device_id] = device_store
return store

View File

@ -20,6 +20,7 @@ setup(
"keyring", "keyring",
"logbook", "logbook",
"peewee", "peewee",
"dbus-python",
"typing;python_version<'3.5'", "typing;python_version<'3.5'",
"matrix-nio" "matrix-nio"
], ],