Optional encryption support

This commit is contained in:
Andrew Morgan 2020-02-24 22:13:28 +00:00
parent f40373837c
commit 1eacb2926e
4 changed files with 83 additions and 40 deletions

View file

@ -46,6 +46,7 @@ async def send_text_to_room(
room_id,
"m.room.message",
content,
ignore_unverified_devices=True,
)
except SendRetryError:
logger.exception(f"Unable to send message response to {room_id}")

View file

@ -41,24 +41,20 @@ class Config(object):
handler.setFormatter(formatter)
logger.addHandler(handler)
# Database setup
self.database_filepath = self._get_cfg(["database", "filepath"], required=True)
# Storage setup
self.database_filepath = self._get_cfg(["storage", "database_filepath"], required=True)
self.store_filepath = self._get_cfg(["storage", "store_filepath"], required=True)
# Matrix bot account setup
self.user_id = self._get_cfg(["matrix", "user_id"], required=True)
if not re.match("@.*:.*", self.user_id):
raise ConfigError("matrix.user_id must be in the form @name:domain")
self.access_token = self._get_cfg(["matrix", "access_token"], required=True)
self.device_id = self._get_cfg(["matrix", "device_id"])
if not self.device_id:
logger.warning(
"Config option matrix.device_id is not provided, which means "
"that end-to-end encryption won't work correctly"
)
self.user_password = self._get_cfg(["matrix", "user_password"], required=True)
self.device_id = self._get_cfg(["matrix", "device_id"], required=True)
self.device_name = self._get_cfg(["matrix", "device_name"], default="nio-template")
self.homeserver_url = self._get_cfg(["matrix", "homeserver_url"], required=True)
self.enable_encryption = self._get_cfg(["matrix", "enable_encryption"], default=False)
self.command_prefix = self._get_cfg(["command_prefix"], default="!c") + " "
@ -75,8 +71,6 @@ class Config(object):
ConfigError: If required is specified and the object is not found
(and there is no default value provided), this error will be raised
"""
path_str = '.'.join(path)
# Sift through the the config until we reach our option
config = self.config
for name in path:
@ -86,7 +80,7 @@ class Config(object):
if config is None:
# Raise an error if it was required
if required or not default:
raise ConfigError(f"Config option {path_str} is required")
raise ConfigError(f"Config option {'.'.join(path)} is required")
# or return the default value
return default

77
main.py
View file

@ -2,17 +2,22 @@
import logging
import asyncio
from time import sleep
from nio import (
AsyncClient,
AsyncClientConfig,
RoomMessageText,
InviteEvent,
SyncError,
LoginError,
LocalProtocolError,
)
from aiohttp import (
ServerDisconnectedError,
ClientConnectionError,
)
from callbacks import Callbacks
from config import Config
from storage import Storage
from sync_token import SyncToken
logger = logging.getLogger(__name__)
@ -28,41 +33,73 @@ async def main():
client_config = AsyncClientConfig(
max_limit_exceeded=0,
max_timeouts=0,
store_sync_tokens=True,
)
# Initialize the matrix client
if config.enable_encryption:
store_path = config.store_filepath
else:
store_path = None
client = AsyncClient(
config.homeserver_url,
config.user_id,
device_id=config.device_id,
store_path=store_path,
config=client_config,
)
# Assign an access token to the bot instead of logging in and creating a new device
client.access_token = config.access_token
# Set up event callbacks
callbacks = Callbacks(client, store, config)
client.add_event_callback(callbacks.message, (RoomMessageText,))
client.add_event_callback(callbacks.invite, (InviteEvent,))
# Create a new sync token, attempting to load one from the database if it has one already
sync_token = SyncToken(store)
# Sync loop
# Keep trying to reconnect on failure (with some time in-between)
while True:
# Sync with the server
sync_response = await client.sync(timeout=30000, full_state=True,
since=sync_token.token)
try:
# Try to login with the configured username/password
try:
login_response = await client.login(
password=config.user_password,
device_name=config.device_name,
)
# Check if the sync had an error
if type(sync_response) == SyncError:
logger.warning("Error in client sync: %s", sync_response.message)
continue
# Check if login failed
if type(login_response) == LoginError:
logger.error(f"Failed to login: %s", login_response.message)
return False
except LocalProtocolError as e:
# There's an edge case here where the user enables encryption but hasn't installed
# the correct C dependencies. In that case, a LocalProtocolError is raised on login.
# Warn the user if these conditions are met.
if config.enable_encryption:
logger.fatal(
"Failed to login and encryption is enabled. Have you installed the correct dependencies? "
"https://github.com/poljar/matrix-nio#installation"
)
return False
else:
# We don't know why this was raised. Throw it at the user
logger.fatal("Error logging in: %s", e)
# Save the latest sync token to the database
token = sync_response.next_batch
if token:
sync_token.update(token)
# Login succeeded!
# Sync encryption keys with the server
# Required for participating in encrypted rooms
if client.should_upload_keys:
await client.keys_upload()
logger.info(f"Logged in as {config.user_id}")
await client.sync_forever(timeout=30000, full_state=True)
except (ClientConnectionError, ServerDisconnectedError):
logger.warning("Unable to connect to homeserver, retrying in 15s...")
# Sleep so we don't bombard the server with login requests
sleep(15)
finally:
# Make sure to close the client connection on disconnect
await client.close()
asyncio.get_event_loop().run_until_complete(main())

View file

@ -9,16 +9,27 @@ command_prefix: "!c"
matrix:
# The Matrix User ID of the bot account
user_id: "@bot:example.com"
# The access token of the bot account
access_token: ""
# The device ID given on login
device_id: ABCDEFGHIJ
# Matrix account password
user_password: ""
# The URL of the homeserver to connect to
homeserver_url: https://example.com
# The device ID that is **non pre-existing** device
# If this device ID already exists, messages will be dropped silently in encrypted rooms
device_id: ABCDEFGHIJ
# What to name the logged in device
device_name: nio-template
# End-to-end encryption support
#
# Enabling this requires installing the matrix-nio encryption dependencies
# as described here: https://github.com/poljar/matrix-nio#installation
enable_encryption: true
database:
storage:
# The path to the database
filepath: "bot.db"
database_filepath: "bot.db"
# The path to a directory for internal bot storage
# containing encryption keys, sync tokens, etc.
store_filepath: "./store"
# Logging setup
logging: