From 163f3efdc5a962e5aa9de5f4cc1ae234cf1ed31d Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sat, 13 May 2023 15:58:00 +0300 Subject: [PATCH] Start switching webhook secrets to not contain room ID --- github/api/webhook.py | 6 +++++- github/bot.py | 1 + github/webhook/manager.py | 24 ++++++++++++++++++++++-- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/github/api/webhook.py b/github/api/webhook.py index fb7d8c4..633bcbf 100644 --- a/github/api/webhook.py +++ b/github/api/webhook.py @@ -33,6 +33,7 @@ class WebhookInfo(Protocol): secret: str + old_secret: str class HandlerFunc(Protocol): @@ -109,7 +110,10 @@ async def _handle(self, request: web.Request, webhook_info: 'WebhookInfo') -> we secret = webhook_info.secret.encode("utf-8") digest = f"sha1={hmac.new(secret, text_binary, hashlib.sha1).hexdigest()}" if not hmac.compare_digest(signature, digest): - return web.Response(status=401, text="Invalid signature") + old_secret = webhook_info.old_secret.encode("utf-8") + old_digest = f"sha1={hmac.new(old_secret, text_binary, hashlib.sha1).hexdigest()}" + if not hmac.compare_digest(signature, old_digest): + return web.Response(status=401, text="Invalid signature") try: data = json.loads(text) except json.JSONDecodeError: diff --git a/github/bot.py b/github/bot.py index dd1f423..b4aa95b 100644 --- a/github/bot.py +++ b/github/bot.py @@ -57,6 +57,7 @@ async def start(self) -> None: self.clients.load_db() self.register_handler_class(self.webhook_receiver) + self.register_handler_class(self.webhook_manager) self.register_handler_class(self.clients) self.register_handler_class(self.commands) diff --git a/github/webhook/manager.py b/github/webhook/manager.py index dfb6d93..8780235 100644 --- a/github/webhook/manager.py +++ b/github/webhook/manager.py @@ -21,7 +21,8 @@ from sqlalchemy import MetaData, Table, Column, String, Integer, UniqueConstraint, and_ from sqlalchemy.engine.base import Engine -from mautrix.types import UserID, RoomID +from mautrix.types import UserID, RoomID, EventType, StateEvent, RoomTombstoneStateEventContent +from maubot.handlers import event from ..util import UUIDType @@ -61,13 +62,20 @@ def __setattr__(self, key: str, value: Any) -> None: super().__setattr__(key, value) @property - def secret(self) -> str: + def old_secret(self) -> str: secret = hmac.new(key=self._secret_key, digestmod=hashlib.sha256) secret.update(self.id.bytes) secret.update(self.user_id.encode("utf-8")) secret.update(self.room_id.encode("utf-8")) return secret.hexdigest() + @property + def secret(self) -> str: + secret = hmac.new(key=self._secret_key, digestmod=hashlib.sha256) + secret.update(self.id.bytes) + secret.update(self.user_id.encode("utf-8")) + return secret.hexdigest() + class WebhookManager: _table: Table @@ -99,6 +107,18 @@ def create(self, repo: str, user_id: UserID, room_id: RoomID) -> WebhookInfo: self._webhooks[info.id] = info return info + @event.on(EventType.ROOM_TOMBSTONE) + async def handle_room_upgrade(self, evt: StateEvent) -> None: + assert isinstance(evt.content, RoomTombstoneStateEventContent) + self._db.execute( + self._table.update() + .where(self._table.c.room_id == evt.room_id) + .values(room_id=evt.content.replacement_room) + ) + for webhook in self._webhooks.values(): + if webhook.room_id == evt.room_id: + webhook.room_id = evt.content.replacement_room + def set_github_id(self, info: WebhookInfo, github_id: int) -> WebhookInfo: self._db.execute(self._table.update() .where(self._table.c.id == info.id)