mirror of
https://github.com/chatmail/relay.git
synced 2026-05-19 20:38:05 +00:00
store tokens on a per-maildir basis
This commit is contained in:
@@ -47,8 +47,8 @@ class Notifier:
|
|||||||
if token_path.exists():
|
if token_path.exists():
|
||||||
return token_path.read_text()
|
return token_path.read_text()
|
||||||
|
|
||||||
def new_message_for_guid(self, guid):
|
def new_message_for_mbox(self, mbox):
|
||||||
self.to_notify_queue.put(guid)
|
self.to_notify_queue.put(mbox)
|
||||||
|
|
||||||
def thread_run_loop(self):
|
def thread_run_loop(self):
|
||||||
requests_session = requests.Session()
|
requests_session = requests.Session()
|
||||||
@@ -56,8 +56,8 @@ class Notifier:
|
|||||||
self.thread_run_one(requests_session)
|
self.thread_run_one(requests_session)
|
||||||
|
|
||||||
def thread_run_one(self, requests_session):
|
def thread_run_one(self, requests_session):
|
||||||
guid = self.to_notify_queue.get()
|
mbox = self.to_notify_queue.get()
|
||||||
token = self.get_token(guid)
|
token = self.get_token(mbox)
|
||||||
if token:
|
if token:
|
||||||
response = requests_session.post(
|
response = requests_session.post(
|
||||||
"https://notifications.delta.chat/notify",
|
"https://notifications.delta.chat/notify",
|
||||||
@@ -67,7 +67,7 @@ class Notifier:
|
|||||||
if response.status_code == 410:
|
if response.status_code == 410:
|
||||||
# 410 Gone status code
|
# 410 Gone status code
|
||||||
# means the token is no longer valid.
|
# means the token is no longer valid.
|
||||||
self.del_token(guid)
|
self.del_token(mbox)
|
||||||
|
|
||||||
|
|
||||||
def handle_dovecot_protocol(rfile, wfile, notifier):
|
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):
|
def handle_dovecot_request(msg, transactions, notifier):
|
||||||
# see https://doc.dovecot.org/3.0/developer_manual/design/dict_protocol/
|
# see https://doc.dovecot.org/3.0/developer_manual/design/dict_protocol/
|
||||||
|
logging.warning("handling request: %r", msg)
|
||||||
short_command = msg[0]
|
short_command = msg[0]
|
||||||
parts = msg[1:].split("\t")
|
parts = msg[1:].split("\t")
|
||||||
if short_command == DICTPROXY_LOOKUP_CHAR:
|
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"
|
return "N\n"
|
||||||
elif short_command == DICTPROXY_ITERATE_CHAR:
|
elif short_command == DICTPROXY_ITERATE_CHAR:
|
||||||
# Empty line means ITER_FINISHED.
|
# Empty line means ITER_FINISHED.
|
||||||
@@ -103,32 +113,25 @@ def handle_dovecot_request(msg, transactions, notifier):
|
|||||||
transaction_id = parts[0]
|
transaction_id = parts[0]
|
||||||
|
|
||||||
if short_command == DICTPROXY_BEGIN_TRANSACTION_CHAR:
|
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:
|
elif short_command == DICTPROXY_COMMIT_TRANSACTION_CHAR:
|
||||||
# returns whether it failed or succeeded.
|
# 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:
|
elif short_command == DICTPROXY_SET_CHAR:
|
||||||
# See header of
|
# For documentation on key structure see
|
||||||
# <https://github.com/dovecot/core/blob/5e7965632395793d9355eb906b173bf28d2a10ca/src/lib-storage/mailbox-attribute.h>
|
# <https://github.com/dovecot/core/blob/5e7965632395793d9355eb906b173bf28d2a10ca/src/lib-storage/mailbox-attribute.h>
|
||||||
# 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("/")
|
keyname = parts[1].split("/")
|
||||||
value = parts[2] if len(parts) > 2 else ""
|
value = parts[2] if len(parts) > 2 else ""
|
||||||
|
mbox = transactions[transaction_id]["mbox"]
|
||||||
if keyname[0] == "priv" and keyname[2] == "devicetoken":
|
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":
|
elif keyname[0] == "priv" and keyname[2] == "messagenew":
|
||||||
notifier.new_message_for_guid(keyname[1])
|
notifier.new_message_for_mbox(mbox)
|
||||||
else:
|
else:
|
||||||
# Transaction failed.
|
# Transaction failed.
|
||||||
transactions[transaction_id] = "F\n"
|
transactions[transaction_id]["res"] = "F\n"
|
||||||
|
|
||||||
|
|
||||||
class ThreadedUnixStreamServer(ThreadingMixIn, UnixStreamServer):
|
class ThreadedUnixStreamServer(ThreadingMixIn, UnixStreamServer):
|
||||||
|
|||||||
@@ -37,44 +37,42 @@ def test_notifier_delete_without_set(notifier):
|
|||||||
|
|
||||||
|
|
||||||
def test_handle_dovecot_request_lookup_fails(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"
|
assert res == "N\n"
|
||||||
|
|
||||||
|
|
||||||
def test_handle_dovecot_request_happy_path(notifier):
|
def test_handle_dovecot_request_happy_path(notifier):
|
||||||
transactions = {}
|
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
|
# set device token in a transaction
|
||||||
tx = "1111"
|
tx = "1111"
|
||||||
msg = f"B{tx}\tuser"
|
msg = f"B{tx}\tuser@example.org"
|
||||||
res = handle_dovecot_request(msg, transactions, notifier)
|
res = handle_dovecot_request(msg, transactions, notifier)
|
||||||
assert not res and notifier.get_token("guid00") is None
|
assert not res and notifier.get_token("user@example.org") is None
|
||||||
assert transactions == {tx: "O\n"}
|
assert transactions == {tx: dict(mbox="user@example.org", res="O\n")}
|
||||||
|
|
||||||
msg = f"S{tx}\tpriv/guid00/devicetoken\t01234"
|
msg = f"S{tx}\tpriv/guid00/devicetoken\t01234"
|
||||||
res = handle_dovecot_request(msg, transactions, notifier)
|
res = handle_dovecot_request(msg, transactions, notifier)
|
||||||
assert not res
|
assert not res
|
||||||
assert len(transactions) == 1
|
assert len(transactions) == 1
|
||||||
assert notifier.get_token("guid00") == "01234"
|
assert notifier.get_token("user@example.org") == "01234"
|
||||||
|
|
||||||
msg = f"C{tx}"
|
msg = f"C{tx}"
|
||||||
res = handle_dovecot_request(msg, transactions, notifier)
|
res = handle_dovecot_request(msg, transactions, notifier)
|
||||||
assert res == "O\n"
|
assert res == "O\n"
|
||||||
assert len(transactions) == 0
|
assert len(transactions) == 0
|
||||||
assert notifier.get_token("guid00") == "01234"
|
assert notifier.get_token("user@example.org") == "01234"
|
||||||
|
|
||||||
# trigger notification for incoming message
|
# 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"
|
msg = f"S{tx}\tpriv/guid00/messagenew"
|
||||||
assert handle_dovecot_request(msg, transactions, notifier) is None
|
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 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
|
assert not transactions
|
||||||
|
|
||||||
|
|
||||||
@@ -83,7 +81,7 @@ def test_handle_dovecot_protocol_set_devicetoken(notifier):
|
|||||||
b"\n".join(
|
b"\n".join(
|
||||||
[
|
[
|
||||||
b"HELLO",
|
b"HELLO",
|
||||||
b"Btx00\tuser",
|
b"Btx00\tuser@example.org",
|
||||||
b"Stx00\tpriv/guid00/devicetoken\t01234",
|
b"Stx00\tpriv/guid00/devicetoken\t01234",
|
||||||
b"Ctx00",
|
b"Ctx00",
|
||||||
]
|
]
|
||||||
@@ -91,8 +89,32 @@ def test_handle_dovecot_protocol_set_devicetoken(notifier):
|
|||||||
)
|
)
|
||||||
wfile = io.BytesIO()
|
wfile = io.BytesIO()
|
||||||
handle_dovecot_protocol(rfile, wfile, notifier)
|
handle_dovecot_protocol(rfile, wfile, notifier)
|
||||||
assert notifier.get_token("guid00") == "01234"
|
|
||||||
assert wfile.getvalue() == b"O\n"
|
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):
|
def test_handle_dovecot_protocol_iterate(notifier):
|
||||||
@@ -114,7 +136,7 @@ def test_handle_dovecot_protocol_messagenew(notifier):
|
|||||||
b"\n".join(
|
b"\n".join(
|
||||||
[
|
[
|
||||||
b"HELLO",
|
b"HELLO",
|
||||||
b"Btx01\tuser",
|
b"Btx01\tuser@example.org",
|
||||||
b"Stx01\tpriv/guid00/messagenew",
|
b"Stx01\tpriv/guid00/messagenew",
|
||||||
b"Ctx01",
|
b"Ctx01",
|
||||||
]
|
]
|
||||||
@@ -123,7 +145,7 @@ def test_handle_dovecot_protocol_messagenew(notifier):
|
|||||||
wfile = io.BytesIO()
|
wfile = io.BytesIO()
|
||||||
handle_dovecot_protocol(rfile, wfile, notifier)
|
handle_dovecot_protocol(rfile, wfile, notifier)
|
||||||
assert wfile.getvalue() == b"O\n"
|
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
|
assert notifier.to_notify_queue.qsize() == 0
|
||||||
|
|
||||||
|
|
||||||
@@ -139,12 +161,12 @@ def test_notifier_thread_run(notifier):
|
|||||||
|
|
||||||
return Result()
|
return Result()
|
||||||
|
|
||||||
notifier.set_token("guid00", "01234")
|
notifier.set_token("user@example.org", "01234")
|
||||||
notifier.new_message_for_guid("guid00")
|
notifier.new_message_for_mbox("user@example.org")
|
||||||
notifier.thread_run_one(ReqMock())
|
notifier.thread_run_one(ReqMock())
|
||||||
url, data, timeout = requests[0]
|
url, data, timeout = requests[0]
|
||||||
assert data == "01234"
|
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):
|
def test_notifier_thread_run_gone_removes_token(notifier):
|
||||||
@@ -159,10 +181,10 @@ def test_notifier_thread_run_gone_removes_token(notifier):
|
|||||||
|
|
||||||
return Result()
|
return Result()
|
||||||
|
|
||||||
notifier.set_token("guid00", "01234")
|
notifier.set_token("user@example.org", "01234")
|
||||||
notifier.new_message_for_guid("guid00")
|
notifier.new_message_for_mbox("user@example.org")
|
||||||
assert notifier.get_token("guid00") == "01234"
|
assert notifier.get_token("user@example.org") == "01234"
|
||||||
notifier.thread_run_one(ReqMock())
|
notifier.thread_run_one(ReqMock())
|
||||||
url, data, timeout = requests[0]
|
url, data, timeout = requests[0]
|
||||||
assert data == "01234"
|
assert data == "01234"
|
||||||
assert notifier.get_token("guid00") is None
|
assert notifier.get_token("user@example.org") is None
|
||||||
|
|||||||
Reference in New Issue
Block a user