From 8ee6ca1b8072eb9613930a103c7eec1b1f3cb3f4 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Wed, 27 Mar 2024 12:16:18 +0100 Subject: [PATCH] store tokens on a per-maildir basis --- chatmaild/src/chatmaild/metadata.py | 43 +++++------ .../src/chatmaild/tests/test_metadata.py | 72 ++++++++++++------- 2 files changed, 70 insertions(+), 45 deletions(-) diff --git a/chatmaild/src/chatmaild/metadata.py b/chatmaild/src/chatmaild/metadata.py index 22b19e5..9fd51a4 100644 --- a/chatmaild/src/chatmaild/metadata.py +++ b/chatmaild/src/chatmaild/metadata.py @@ -47,8 +47,8 @@ class Notifier: if token_path.exists(): return token_path.read_text() - def new_message_for_guid(self, guid): - self.to_notify_queue.put(guid) + def new_message_for_mbox(self, mbox): + self.to_notify_queue.put(mbox) def thread_run_loop(self): requests_session = requests.Session() @@ -56,8 +56,8 @@ class Notifier: self.thread_run_one(requests_session) def thread_run_one(self, requests_session): - guid = self.to_notify_queue.get() - token = self.get_token(guid) + mbox = self.to_notify_queue.get() + token = self.get_token(mbox) if token: response = requests_session.post( "https://notifications.delta.chat/notify", @@ -67,7 +67,7 @@ class Notifier: if response.status_code == 410: # 410 Gone status code # means the token is no longer valid. - self.del_token(guid) + self.del_token(mbox) def handle_dovecot_protocol(rfile, wfile, notifier): @@ -88,9 +88,19 @@ def handle_dovecot_protocol(rfile, wfile, notifier): def handle_dovecot_request(msg, transactions, notifier): # see https://doc.dovecot.org/3.0/developer_manual/design/dict_protocol/ + logging.warning("handling request: %r", msg) short_command = msg[0] parts = msg[1:].split("\t") if short_command == DICTPROXY_LOOKUP_CHAR: + # Lpriv/43f5f508a7ea0366dff30200c15250e3/devicetoken\tlkj123poi@c2.testrun.org + keyparts = parts[0].split("/") + if keyparts[0] == "priv": + # guid = keyparts[1] + keyname = keyparts[2] + mbox = parts[1] + if keyname == "devicetoken": + return f"O{notifier.get_token(mbox)}\n" + logging.warning("lookup ignored: %r", msg) return "N\n" elif short_command == DICTPROXY_ITERATE_CHAR: # Empty line means ITER_FINISHED. @@ -103,32 +113,25 @@ def handle_dovecot_request(msg, transactions, notifier): transaction_id = parts[0] if short_command == DICTPROXY_BEGIN_TRANSACTION_CHAR: - transactions[transaction_id] = "O\n" + mbox = parts[1] + transactions[transaction_id] = dict(mbox=mbox, res="O\n") elif short_command == DICTPROXY_COMMIT_TRANSACTION_CHAR: # returns whether it failed or succeeded. - return transactions.pop(transaction_id, "N\n") + return transactions.pop(transaction_id)["res"] elif short_command == DICTPROXY_SET_CHAR: - # See header of + # For documentation on key structure see # - # for the documentation on the structure of the key. - - # Request GETMETADATA "INBOX" /private/chatmail - # results in a query for - # priv/dd72550f05eadc65542a1200cac67ad7/chatmail - # - # Request GETMETADATA "" /private/chatmail - # results in - # priv/dd72550f05eadc65542a1200cac67ad7/vendor/vendor.dovecot/pvt/server/chatmail keyname = parts[1].split("/") value = parts[2] if len(parts) > 2 else "" + mbox = transactions[transaction_id]["mbox"] if keyname[0] == "priv" and keyname[2] == "devicetoken": - notifier.set_token(keyname[1], value) + notifier.set_token(mbox, value) elif keyname[0] == "priv" and keyname[2] == "messagenew": - notifier.new_message_for_guid(keyname[1]) + notifier.new_message_for_mbox(mbox) else: # Transaction failed. - transactions[transaction_id] = "F\n" + transactions[transaction_id]["res"] = "F\n" class ThreadedUnixStreamServer(ThreadingMixIn, UnixStreamServer): diff --git a/chatmaild/src/chatmaild/tests/test_metadata.py b/chatmaild/src/chatmaild/tests/test_metadata.py index cd9c948..c8d01e4 100644 --- a/chatmaild/src/chatmaild/tests/test_metadata.py +++ b/chatmaild/src/chatmaild/tests/test_metadata.py @@ -37,44 +37,42 @@ def test_notifier_delete_without_set(notifier): def test_handle_dovecot_request_lookup_fails(notifier): - res = handle_dovecot_request("Lpriv/123/chatmail", {}, notifier) + res = handle_dovecot_request("Lpriv/123/chatmail\tuser@example.org", {}, notifier) assert res == "N\n" def test_handle_dovecot_request_happy_path(notifier): transactions = {} - # lookups return the same NOTFOUND result - res = handle_dovecot_request("Lpriv/123/chatmail", transactions, notifier) - assert res == "N\n" - assert notifier.get_token("guid00") is None and not transactions - # set device token in a transaction tx = "1111" - msg = f"B{tx}\tuser" + msg = f"B{tx}\tuser@example.org" res = handle_dovecot_request(msg, transactions, notifier) - assert not res and notifier.get_token("guid00") is None - assert transactions == {tx: "O\n"} + assert not res and notifier.get_token("user@example.org") is None + assert transactions == {tx: dict(mbox="user@example.org", res="O\n")} msg = f"S{tx}\tpriv/guid00/devicetoken\t01234" res = handle_dovecot_request(msg, transactions, notifier) assert not res assert len(transactions) == 1 - assert notifier.get_token("guid00") == "01234" + assert notifier.get_token("user@example.org") == "01234" msg = f"C{tx}" res = handle_dovecot_request(msg, transactions, notifier) assert res == "O\n" assert len(transactions) == 0 - assert notifier.get_token("guid00") == "01234" + assert notifier.get_token("user@example.org") == "01234" # trigger notification for incoming message - assert handle_dovecot_request(f"B{tx}\tuser", transactions, notifier) is None + assert ( + handle_dovecot_request(f"B{tx}\tuser@example.org", transactions, notifier) + is None + ) msg = f"S{tx}\tpriv/guid00/messagenew" assert handle_dovecot_request(msg, transactions, notifier) is None - assert notifier.to_notify_queue.get() == "guid00" + assert notifier.to_notify_queue.get() == "user@example.org" assert notifier.to_notify_queue.qsize() == 0 - assert handle_dovecot_request(f"C{tx}\tuser", transactions, notifier) == "O\n" + assert handle_dovecot_request(f"C{tx}", transactions, notifier) == "O\n" assert not transactions @@ -83,7 +81,7 @@ def test_handle_dovecot_protocol_set_devicetoken(notifier): b"\n".join( [ b"HELLO", - b"Btx00\tuser", + b"Btx00\tuser@example.org", b"Stx00\tpriv/guid00/devicetoken\t01234", b"Ctx00", ] @@ -91,8 +89,32 @@ def test_handle_dovecot_protocol_set_devicetoken(notifier): ) wfile = io.BytesIO() handle_dovecot_protocol(rfile, wfile, notifier) - assert notifier.get_token("guid00") == "01234" assert wfile.getvalue() == b"O\n" + assert notifier.get_token("user@example.org") == "01234" + + +def test_handle_dovecot_protocol_set_get_devicetoken(notifier): + rfile = io.BytesIO( + b"\n".join( + [ + b"HELLO", + b"Btx00\tuser@example.org", + b"Stx00\tpriv/guid00/devicetoken\t01234", + b"Ctx00", + ] + ) + ) + wfile = io.BytesIO() + handle_dovecot_protocol(rfile, wfile, notifier) + assert notifier.get_token("user@example.org") == "01234" + assert wfile.getvalue() == b"O\n" + + rfile = io.BytesIO( + b"\n".join([b"HELLO", b"Lpriv/0123/devicetoken\tuser@example.org"]) + ) + wfile = io.BytesIO() + handle_dovecot_protocol(rfile, wfile, notifier) + assert wfile.getvalue() == b"O01234\n" def test_handle_dovecot_protocol_iterate(notifier): @@ -114,7 +136,7 @@ def test_handle_dovecot_protocol_messagenew(notifier): b"\n".join( [ b"HELLO", - b"Btx01\tuser", + b"Btx01\tuser@example.org", b"Stx01\tpriv/guid00/messagenew", b"Ctx01", ] @@ -123,7 +145,7 @@ def test_handle_dovecot_protocol_messagenew(notifier): wfile = io.BytesIO() handle_dovecot_protocol(rfile, wfile, notifier) assert wfile.getvalue() == b"O\n" - assert notifier.to_notify_queue.get() == "guid00" + assert notifier.to_notify_queue.get() == "user@example.org" assert notifier.to_notify_queue.qsize() == 0 @@ -139,12 +161,12 @@ def test_notifier_thread_run(notifier): return Result() - notifier.set_token("guid00", "01234") - notifier.new_message_for_guid("guid00") + notifier.set_token("user@example.org", "01234") + notifier.new_message_for_mbox("user@example.org") notifier.thread_run_one(ReqMock()) url, data, timeout = requests[0] assert data == "01234" - assert notifier.get_token("guid00") == "01234" + assert notifier.get_token("user@example.org") == "01234" def test_notifier_thread_run_gone_removes_token(notifier): @@ -159,10 +181,10 @@ def test_notifier_thread_run_gone_removes_token(notifier): return Result() - notifier.set_token("guid00", "01234") - notifier.new_message_for_guid("guid00") - assert notifier.get_token("guid00") == "01234" + notifier.set_token("user@example.org", "01234") + notifier.new_message_for_mbox("user@example.org") + assert notifier.get_token("user@example.org") == "01234" notifier.thread_run_one(ReqMock()) url, data, timeout = requests[0] assert data == "01234" - assert notifier.get_token("guid00") is None + assert notifier.get_token("user@example.org") is None