ui: Rework the way the dbus thread and main thread communicate.

This commit is contained in:
Damir Jelić 2019-05-13 16:29:59 +02:00
parent 1cbe5b8a6d
commit 42450b51c7
6 changed files with 356 additions and 94 deletions

View File

@ -9,12 +9,15 @@ from nio import (AsyncClient, ClientConfig, EncryptionError,
KeyVerificationEvent, LocalProtocolError,
KeyVerificationStart, KeyVerificationKey, KeyVerificationMac)
from nio.store import SqliteStore
from nio.crypto import Sas
from pantalaimon.log import logger
from pantalaimon.thread_messages import (
DevicesMessage,
DeviceAuthStringMessage,
InfoMessage
InviteSasSignal,
ShowSasSignal,
SasDoneSignal,
DaemonResponse
)
@ -72,9 +75,8 @@ class PanClient(AsyncClient):
}
}
async def send_info(self, string):
"""Send a info message to the UI thread."""
message = InfoMessage(string)
async def send_message(self, message):
"""Send a thread message to the UI thread."""
await self.queue.put(message)
async def sync_tasks(self, response):
@ -125,10 +127,11 @@ class PanClient(AsyncClient):
logger.info(f"{event.sender} via {event.from_device} has started "
f"a key verification process.")
message = DeviceStartSasMessage(
message = InviteSasSignal(
self.user_id,
event.sender,
event.from_device
event.from_device,
event.transaction_id
)
task = loop.create_task(
@ -144,10 +147,11 @@ class PanClient(AsyncClient):
device = sas.other_olm_device
emoji = sas.get_emoji()
message = DeviceAuthStringMessage(
message = ShowSasSignal(
self.user_id,
device.user_id,
device.id,
sas.transaction_id,
emoji
)
@ -163,11 +167,14 @@ class PanClient(AsyncClient):
device = sas.other_olm_device
if sas.verified:
task = loop.create_task(
self.send_info(f"Device {device.id} of user "
f"{device.user_id} succesfully "
f"verified.")
)
task = loop.create_task(self.send_message(
SasDoneSignal(
self.user_id,
device.user_id,
device.id,
sas.transaction_id
)
))
self.key_verificatins_tasks.append(task)
def start_loop(self):
@ -199,13 +206,42 @@ class PanClient(AsyncClient):
sas = self.get_active_sas(user_id, device_id)
if not sas:
await self.send_info("No such verification process found.")
await self.send_message(
DaemonResponse(
message.message_id,
self.user_id,
Sas._txid_error[0],
Sas._txid_error[1]
)
)
return
try:
await self.accept_key_verification(sas.transaction_id)
except (LocalProtocolError, ClientConnectionError) as e:
await self.send_info(f"Error accepting key verification: {e}")
await self.send_message(
DaemonResponse(
message.message_id,
self.user_id,
"m.ok",
"Successfully accepted the key verification request"
))
except LocalProtocolError as e:
await self.send_message(
DaemonResponse(
message.message_id,
self.user_id,
Sas._unexpected_message_error[0],
e
))
except ClientConnectionError as e:
await self.send_message(
DaemonResponse(
message.message_id,
self.user_id,
"m.connection_error",
e
))
async def confirm_sas(self, message):
user_id = message.user_id
@ -214,20 +250,49 @@ class PanClient(AsyncClient):
sas = self.get_active_sas(user_id, device_id)
if not sas:
await self.send_info("No such verification process found.")
await self.send_message(
DaemonResponse(
message.message_id,
self.user_id,
Sas._txid_error[0],
Sas._txid_error[1]
)
)
return
try:
await self.confirm_short_auth_string(sas.transaction_id)
except ClientConnectionError as e:
await self.send_info(f"Error confirming short auth string: {e}")
await self.send_message(
DaemonResponse(
message.message_id,
self.user_id,
"m.connection_error",
e
))
return
device = sas.other_olm_device
if sas.verified:
await self.send_info(f"Device {device.id} of user {device.user_id}"
f" succesfully verified.")
await self.send_message(
SasDoneSignal(
self.user_id,
device.user_id,
device.id,
sas.transaction_id
)
)
else:
await self.send_info(f"Waiting for {device.user_id} to confirm...")
await self.send_message(
DaemonResponse(
message.message_id,
self.user_id,
"m.ok",
f"Waiting for {device.user_id} to confirm."
))
async def loop_stop(self):
"""Stop the client loop."""

View File

@ -24,8 +24,9 @@ from pantalaimon.thread_messages import (
ExportKeysMessage,
ImportKeysMessage,
DeviceConfirmSasMessage,
DeviceAcceptSasMessage,
InfoMessage
SasMessage,
AcceptSasMessage,
DaemonResponse
)
@ -89,7 +90,7 @@ class ProxyDaemon:
pan_client.start_loop()
async def _verify_device(self, client, device):
async def _verify_device(self, message_id, client, device):
ret = client.verify_device(device)
if ret:
@ -100,9 +101,9 @@ class ProxyDaemon:
f"{device.user_id} already verified")
logger.info(msg)
await self.send_info(msg)
await self.send_response(message_id, client.user_id, "m.ok", msg)
async def _unverify_device(self, client, device):
async def _unverify_device(self, message_id, client, device):
ret = client.unverify_device(device)
if ret:
@ -113,11 +114,11 @@ class ProxyDaemon:
f"{device.user_id} already unverified")
logger.info(msg)
await self.send_info(msg)
await self.send_response(message_id, client.user_id, "m.ok", msg)
async def send_info(self, string):
"""Send a info message to the UI thread."""
message = InfoMessage(string)
async def send_response(self, message_id, pan_user, code, message):
"""Send a thread response message to the UI thread."""
message = DaemonResponse(message_id, pan_user, code, message)
await self.send_queue.put(message)
async def receive_message(self, message):
@ -125,8 +126,7 @@ class ProxyDaemon:
if isinstance(
message,
(DeviceVerifyMessage, DeviceUnverifyMessage,
DeviceConfirmSasMessage, DeviceAcceptSasMessage)
(DeviceVerifyMessage, DeviceUnverifyMessage)
):
device = client.device_store[message.user_id].get(
@ -137,15 +137,22 @@ class ProxyDaemon:
if not device:
msg = (f"No device found for {message.user_id} and "
f"{message.device_id}")
await self.send_info(msg)
await self.send_response(
message.message_id,
message.pan_user,
"m.unknown_device",
msg
)
logger.info(msg)
return
if isinstance(message, DeviceVerifyMessage):
await self._verify_device(client, device)
await self._verify_device(message.message_id, client, device)
elif isinstance(message, DeviceUnverifyMessage):
await self._unverify_device(client, device)
elif isinstance(message, DeviceAcceptSasMessage):
await self._unverify_device(message.message_id, client, device)
elif isinstance(message, SasMessage):
if isinstance(message, AcceptSasMessage):
await client.accept_sas(message)
elif isinstance(message, DeviceConfirmSasMessage):
await client.confirm_sas(message)
@ -160,12 +167,23 @@ class ProxyDaemon:
info_msg = (f"Error exporting keys for {client.user_id} to"
f" {path} {e}")
logger.info(info_msg)
await self.send_info(info_msg)
await self.send_response(
message.message_id,
client.user_id,
"m.os_error",
str(e)
)
else:
info_msg = (f"Succesfully exported keys for {client.user_id} "
f"to {path}")
logger.info(info_msg)
await self.send_info(info_msg)
await self.send_response(
message.message_id,
client.user_id,
"m.ok",
info_msg
)
elif isinstance(message, ImportKeysMessage):
path = os.path.abspath(os.path.expanduser(message.file_path))
@ -177,12 +195,22 @@ class ProxyDaemon:
info_msg = (f"Error importing keys for {client.user_id} "
f"from {path} {e}")
logger.info(info_msg)
await self.send_info(info_msg)
await self.send_response(
message.message_id,
client.user_id,
"m.os_error",
str(e)
)
else:
info_msg = (f"Succesfully imported keys for {client.user_id} "
f"from {path}")
logger.info(info_msg)
await self.send_info(info_msg)
await self.send_response(
message.message_id,
client.user_id,
"m.ok",
info_msg
)
def get_access_token(self, request):
# type: (aiohttp.web.BaseRequest) -> str

View File

@ -14,7 +14,7 @@ from logbook import StderrHandler
from aiohttp import web
from pantalaimon.ui import GlibT
from pantalaimon.thread_messages import InfoMessage
from pantalaimon.thread_messages import DaemonResponse
from pantalaimon.daemon import ProxyDaemon
from pantalaimon.config import PanConfig, PanConfigError, parse_log_level
from pantalaimon.log import logger
@ -81,8 +81,8 @@ async def message_router(receive_queue, send_queue, proxies):
return None
async def send_info(string):
message = InfoMessage(string)
async def send_info(message_id, pan_user, code, string):
message = DaemonResponse(message_id, pan_user, code, string)
await send_queue.put(message)
while True:
@ -94,7 +94,12 @@ async def message_router(receive_queue, send_queue, proxies):
if not proxy:
msg = f"No pan client found for {message.pan_user}."
logger.warn(msg)
send_info(msg)
await send_info(
message.message_id,
message.pan_user,
"m.unknown_client",
msg
)
await proxy.receive_message(message)

View File

@ -276,15 +276,34 @@ class PanCtl:
self.ctl = self.pan_bus["org.pantalaimon1.control"]
self.devices = self.pan_bus["org.pantalaimon1.devices"]
self.ctl.Info.connect(self.show_info)
self.devices.SasReceived.connect(self.show_sas)
self.own_message_ids = []
def show_info(self, message):
print(message)
self.ctl.Response.connect(self.show_response)
self.devices.VerificationInvite.connect(self.show_sas_invite)
self.devices.VerificationString.connect(self.show_sas)
self.devices.VerificationDone.connect(self.sas_done)
def show_response(self, response_id, pan_user, message):
if response_id not in self.own_message_ids:
return
self.own_message_ids.remove(response_id)
print(message["message"])
def sas_done(self, pan_user, user_id, device_id, _):
print(f"Device {device_id} of user {user_id}"
f" succesfully verified for pan user {pan_user}.")
def show_sas_invite(self, pan_user, user_id, device_id, _):
print(f"{user_id} has started an interactive device "
f"verification for his device {device_id} with pan user "
f"{pan_user}\n"
f"Accept the invitation with the accept-verification command.")
# The emoji printing logic was taken from weechat-matrix and was written by
# dkasak.
def show_sas(self, pan_user, user_id, device_id, emoji):
def show_sas(self, pan_user, user_id, device_id, _, emoji):
emojis = [x[0] for x in emoji]
descriptions = [x[1] for x in emoji]
@ -378,27 +397,39 @@ class PanCtl:
self.list_users()
elif command == "import-keys":
self.ctl.ImportKeys(args.pan_user, args.path, args.passphrase)
self.own_message_ids.append(
self.ctl.ImportKeys(
args.pan_user,
args.path,
args.passphrase
))
elif command == "export-keys":
self.ctl.ExportKeys(args.pan_user, args.path, args.passphrase)
self.own_message_ids.append(
self.ctl.ExportKeys(
args.pan_user,
args.path,
args.passphrase
))
elif command == "list-devices":
self.list_devices(args)
elif command == "accept-verification":
self.devices.AcceptKeyVerification(
args.pan_user,
args.user_id,
args.device_id
)
self.own_message_ids.append(
self.devices.AcceptKeyVerification(
args.pan_user,
args.user_id,
args.device_id
))
elif command == "confirm-verification":
self.devices.ConfirmKeyVerification(
args.pan_user,
args.user_id,
args.device_id
)
self.own_message_ids.append(
self.devices.ConfirmKeyVerification(
args.pan_user,
args.user_id,
args.device_id
))
def main():

View File

@ -7,8 +7,11 @@ class Message:
@attr.s
class InfoMessage(Message):
string = attr.ib()
class DaemonResponse(Message):
message_id = attr.ib()
pan_user = attr.ib()
code = attr.ib()
message = attr.ib()
@attr.s
@ -19,6 +22,7 @@ class DevicesMessage(Message):
@attr.s
class _KeysOperation(Message):
message_id = attr.ib()
pan_user = attr.ib()
file_path = attr.ib()
passphrase = attr.ib()
@ -36,6 +40,7 @@ class ExportKeysMessage(_KeysOperation):
@attr.s
class _VerificationMessage(Message):
message_id = attr.ib()
pan_user = attr.ib()
user_id = attr.ib()
device_id = attr.ib()
@ -52,20 +57,43 @@ class DeviceUnverifyMessage(_VerificationMessage):
@attr.s
class DeviceStartSasMessage(_VerificationMessage):
class SasMessage(_VerificationMessage):
pass
@attr.s
class DeviceAcceptSasMessage(_VerificationMessage):
class DeviceConfirmSasMessage(SasMessage):
pass
@attr.s
class DeviceConfirmSasMessage(_VerificationMessage):
class AcceptSasMessage(SasMessage):
pass
@attr.s
class DeviceAuthStringMessage(_VerificationMessage):
short_string = attr.ib()
class _SasSignal:
pan_user = attr.ib()
user_id = attr.ib()
device_id = attr.ib()
transaction_id = attr.ib()
@attr.s
class StartSasSignal(_SasSignal):
pass
@attr.s
class InviteSasSignal(_SasSignal):
pass
@attr.s
class ShowSasSignal(_SasSignal):
emoji = attr.ib()
@attr.s
class SasDoneSignal(_SasSignal):
pass

View File

@ -12,16 +12,31 @@ from pantalaimon.thread_messages import (
DeviceVerifyMessage,
DeviceUnverifyMessage,
DevicesMessage,
InfoMessage,
DeviceAcceptSasMessage,
AcceptSasMessage,
DeviceConfirmSasMessage,
DeviceAuthStringMessage,
ImportKeysMessage,
ExportKeysMessage,
StartSasSignal,
ShowSasSignal,
InviteSasSignal,
SasDoneSignal,
DaemonResponse
)
from pantalaimon.log import logger
class IdCounter:
def __init__(self):
self._message_id = 0
@property
def message_id(self):
ret = self._message_id
self._message_id += 1
return ret
class Control:
"""
<node>
@ -34,40 +49,59 @@ class Control:
<arg type='s' name='pan_user' direction='in'/>
<arg type='s' name='file_path' direction='in'/>
<arg type='s' name='passphrase' direction='in'/>
<arg type='u' name='id' direction='out'/>
</method>
<method name='ImportKeys'>
<arg type='s' name='pan_user' direction='in'/>
<arg type='s' name='file_path' direction='in'/>
<arg type='s' name='passphrase' direction='in'/>
<arg type='u' name='id' direction='out'/>
</method>
<signal name="Info">
<arg direction="out" type="s" name="message"/>
<signal name="Response">
<arg direction="out" type="i" name="id"/>
<arg direction="out" type="s" name="pan_user"/>
<arg direction="out" type="a{ss}" name="message"/>
</signal>
</interface>
</node>
"""
def __init__(self, queue, user_list=None):
Response = signal()
def __init__(self, queue, user_list, id_counter):
self.users = user_list
self.queue = queue
self.id_counter = id_counter
@property
def message_id(self):
return self.id_counter.message_id
def ListUsers(self):
"""Return the list of pan users."""
return self.users
def ExportKeys(self, pan_user, filepath, passphrase):
message = ExportKeysMessage(pan_user, filepath, passphrase)
message = ExportKeysMessage(
self.message_id,
pan_user,
filepath,
passphrase
)
self.queue.put(message)
return
return message.message_id
def ImportKeys(self, pan_user, filepath, passphrase):
message = ImportKeysMessage(pan_user, filepath, passphrase)
message = ImportKeysMessage(
self.message_id,
pan_user,
filepath,
passphrase
)
self.queue.put(message)
return
Info = signal()
return message.message_id
class Devices:
@ -89,30 +123,64 @@ class Devices:
<arg type='s' name='pan_user' direction='in'/>
<arg type='s' name='user_id' direction='in'/>
<arg type='s' name='device_id' direction='in'/>
<arg type='u' name='id' direction='out'/>
</method>
<method name='ConfirmKeyVerification'>
<arg type='s' name='pan_user' direction='in'/>
<arg type='s' name='user_id' direction='in'/>
<arg type='s' name='device_id' direction='in'/>
<arg type='u' name='id' direction='out'/>
</method>
<signal name="SasReceived">
<signal name="VerificationInvite">
<arg direction="out" type="s" name="pan_user"/>
<arg direction="out" type="s" name="user_id"/>
<arg direction="out" type="s" name="device_id"/>
<arg direction="out" type="s" name="transaction_id"/>
</signal>
<signal name="VerificationString">
<arg direction="out" type="s" name="pan_user"/>
<arg direction="out" type="s" name="user_id"/>
<arg direction="out" type="s" name="device_id"/>
<arg direction="out" type="s" name="transaction_id"/>
<arg direction="out" type="a(ss)" name="emoji"/>
</signal>
<signal name="VerificationCancel">
<arg direction="out" type="s" name="pan_user"/>
<arg direction="out" type="s" name="user_id"/>
<arg direction="out" type="s" name="device_id"/>
<arg direction="out" type="s" name="transaction_id"/>
<arg direction="out" type="s" name="reason"/>
<arg direction="out" type="s" name="code"/>
</signal>
<signal name="VerificationDone">
<arg direction="out" type="s" name="pan_user"/>
<arg direction="out" type="s" name="user_id"/>
<arg direction="out" type="s" name="device_id"/>
<arg direction="out" type="s" name="transaction_id"/>
</signal>
</interface>
</node>
"""
SasReceived = signal()
VerificationInvite = signal()
VerificationCancel = signal()
VerificationString = signal()
VerificationDone = signal()
def __init__(self, queue, device_list):
def __init__(self, queue, device_list, id_counter):
self.device_list = device_list
self.queue = queue
self.id_counter = id_counter
@property
def message_id(self):
return self.id_counter.message_id
def List(self, pan_user):
device_store = self.device_list.get(pan_user, None)
@ -170,14 +238,25 @@ class Devices:
return
def ConfirmKeyVerification(self, pan_user, user_id, device_id):
message = DeviceConfirmSasMessage(pan_user, user_id, device_id)
message = DeviceConfirmSasMessage(
self.message_id,
pan_user,
user_id,
device_id
)
print("HEEEELOOO {}".format(message.message_id))
self.queue.put(message)
return
return message.message_id
def AcceptKeyVerification(self, pan_user, user_id, device_id):
message = DeviceAcceptSasMessage(pan_user, user_id, device_id)
message = AcceptSasMessage(
self.message_id,
pan_user,
user_id,
device_id
)
self.queue.put(message)
return
return message.message_id
def update_devices(self, message):
device_store = self.device_list[message.user_id]
@ -217,8 +296,10 @@ class GlibT:
self.users = self.store.load_all_users()
self.devices = self.store.load_all_devices()
self.control_if = Control(self.send_queue, self.users)
self.device_if = Devices(self.send_queue, self.devices)
id_counter = IdCounter()
self.control_if = Control(self.send_queue, self.users, id_counter)
self.device_if = Devices(self.send_queue, self.devices, id_counter)
self.bus = SessionBus()
self.bus.publish("org.pantalaimon1", self.control_if, self.device_if)
@ -234,16 +315,40 @@ class GlibT:
if isinstance(message, DevicesMessage):
self.device_if.update_devices(message)
elif isinstance(message, DeviceAuthStringMessage):
self.device_if.SasReceived(
elif isinstance(message, InviteSasSignal):
self.device_if.VerificationInvite(
message.pan_user,
message.user_id,
message.device_id,
message.short_string
message.transaction_id
)
elif isinstance(message, InfoMessage):
self.control_if.Info(message.string)
elif isinstance(message, ShowSasSignal):
self.device_if.VerificationString(
message.pan_user,
message.user_id,
message.device_id,
message.transaction_id,
message.emoji,
)
elif isinstance(message, SasDoneSignal):
self.device_if.VerificationDone(
message.pan_user,
message.user_id,
message.device_id,
message.transaction_id,
)
elif isinstance(message, DaemonResponse):
self.control_if.Response(
message.message_id,
message.pan_user,
{
"code": message.code,
"message": message.message
}
)
self.receive_queue.task_done()
return True