irctest/irctest/server_tests/test_resume.py

251 lines
9.6 KiB
Python
Raw Normal View History

2018-12-28 18:43:12 +00:00
"""
<https://github.com/DanielOaks/ircv3-specifications/blob/master+resume/extensions/resume.md>
"""
import secrets
2018-12-28 18:43:12 +00:00
from irctest import cases
2019-05-24 10:16:02 +00:00
from irctest.numerics import RPL_AWAY
2021-02-22 18:02:13 +00:00
ANCIENT_TIMESTAMP = "2006-01-02T15:04:05.999Z"
2018-12-28 18:43:12 +00:00
2021-02-22 18:02:13 +00:00
class ResumeTestCase(cases.BaseServerTestCase):
@cases.SpecificationSelector.requiredBySpecification("Oragono")
2018-12-28 18:43:12 +00:00
def testNoResumeByDefault(self):
2021-02-22 18:02:13 +00:00
self.connectClient(
"bar", capabilities=["batch", "echo-message", "labeled-response"]
)
2018-12-28 18:43:12 +00:00
ms = self.getMessages(1)
2021-02-22 18:02:13 +00:00
resume_messages = [m for m in ms if m.command == "RESUME"]
self.assertEqual(
resume_messages,
[],
"should not see RESUME messages unless explicitly negotiated",
)
@cases.SpecificationSelector.requiredBySpecification("Oragono")
2018-12-28 18:43:12 +00:00
def testResume(self):
2021-02-22 18:02:13 +00:00
chname = "#" + secrets.token_hex(12)
self.connectClient(
"bar", capabilities=["batch", "labeled-response", "server-time"]
)
2018-12-28 18:43:12 +00:00
ms = self.getMessages(1)
2021-02-22 18:02:13 +00:00
welcome = self.connectClient(
"baz",
capabilities=[
"batch",
"labeled-response",
"server-time",
"draft/resume-0.5",
],
)
resume_messages = [m for m in welcome if m.command == "RESUME"]
2018-12-28 18:43:12 +00:00
self.assertEqual(len(resume_messages), 1)
2021-02-22 18:02:13 +00:00
self.assertEqual(resume_messages[0].params[0], "TOKEN")
2018-12-28 18:43:12 +00:00
token = resume_messages[0].params[1]
self.joinChannel(1, chname)
self.joinChannel(2, chname)
2021-02-22 18:02:13 +00:00
self.sendLine(1, "PRIVMSG %s :hello friends" % (chname,))
self.sendLine(1, "PRIVMSG baz :hello friend singular")
2018-12-28 18:43:12 +00:00
self.getMessages(1)
# should receive these messages
2021-02-22 18:02:13 +00:00
privmsgs = [m for m in self.getMessages(2) if m.command == "PRIVMSG"]
2018-12-28 18:43:12 +00:00
self.assertEqual(len(privmsgs), 2)
privmsgs.sort(key=lambda m: m.params[0])
2021-02-22 18:02:13 +00:00
self.assertMessageEqual(
privmsgs[0], command="PRIVMSG", params=[chname, "hello friends"]
)
self.assertMessageEqual(
privmsgs[1], command="PRIVMSG", params=["baz", "hello friend singular"]
)
channelMsgTime = privmsgs[0].tags.get("time")
2018-12-28 18:43:12 +00:00
2018-12-31 00:05:18 +00:00
# tokens MUST be cryptographically secure; therefore, this token should be invalid
# with probability at least 1 - 1/(2**128)
2021-02-22 18:02:13 +00:00
bad_token = "a" * len(token)
2018-12-28 18:43:12 +00:00
self.addClient()
2021-02-22 18:02:13 +00:00
self.sendLine(3, "CAP LS")
self.sendLine(3, "CAP REQ :batch labeled-response server-time draft/resume-0.5")
self.sendLine(3, "NICK tempnick")
self.sendLine(3, "USER tempuser 0 * tempuser")
self.sendLine(3, " ".join(("RESUME", bad_token, ANCIENT_TIMESTAMP)))
2018-12-31 00:05:18 +00:00
# resume with a bad token MUST fail
2018-12-28 18:43:12 +00:00
ms = self.getMessages(3)
2021-02-22 18:02:13 +00:00
resume_err_messages = [
m
for m in ms
if m.command == "FAIL" and m.params[:2] == ["RESUME", "INVALID_TOKEN"]
]
2018-12-31 00:05:18 +00:00
self.assertEqual(len(resume_err_messages), 1)
# however, registration should proceed with the alternative nick
2021-02-22 18:02:13 +00:00
self.sendLine(3, "CAP END")
welcome_msgs = [
m for m in self.getMessages(3) if m.command == "001"
] # RPL_WELCOME
self.assertEqual(welcome_msgs[0].params[0], "tempnick")
2018-12-31 00:05:18 +00:00
self.addClient()
2021-02-22 18:02:13 +00:00
self.sendLine(4, "CAP LS")
self.sendLine(4, "CAP REQ :batch labeled-response server-time draft/resume-0.5")
self.sendLine(4, "NICK tempnick_")
self.sendLine(4, "USER tempuser 0 * tempuser")
2018-12-31 00:05:18 +00:00
# resume with a timestamp in the distant past
2021-02-22 18:02:13 +00:00
self.sendLine(4, " ".join(("RESUME", token, ANCIENT_TIMESTAMP)))
# successful resume does not require CAP END:
# https://github.com/ircv3/ircv3-specifications/pull/306/files#r255318883
2018-12-31 00:05:18 +00:00
ms = self.getMessages(4)
2018-12-28 18:43:12 +00:00
2019-05-24 10:16:02 +00:00
# now, do a valid resume with the correct token
2021-02-22 18:02:13 +00:00
resume_messages = [m for m in ms if m.command == "RESUME"]
2018-12-28 18:43:12 +00:00
self.assertEqual(len(resume_messages), 2)
2021-02-22 18:02:13 +00:00
self.assertEqual(resume_messages[0].params[0], "TOKEN")
2018-12-28 18:43:12 +00:00
new_token = resume_messages[0].params[1]
2021-02-22 18:02:13 +00:00
self.assertNotEqual(
token,
new_token,
"should receive a new, strong resume token; instead got " + new_token,
)
2019-05-24 10:16:02 +00:00
# success message
2021-02-22 18:02:13 +00:00
self.assertMessageEqual(
resume_messages[1], command="RESUME", params=["SUCCESS", "baz"]
)
2018-12-28 18:43:12 +00:00
# test replay of messages
2021-02-22 18:02:13 +00:00
privmsgs = [
m for m in ms if m.command == "PRIVMSG" and m.prefix.startswith("bar")
]
2018-12-28 18:43:12 +00:00
self.assertEqual(len(privmsgs), 2)
privmsgs.sort(key=lambda m: m.params[0])
2021-02-22 18:02:13 +00:00
self.assertMessageEqual(
privmsgs[0], command="PRIVMSG", params=[chname, "hello friends"]
)
self.assertMessageEqual(
privmsgs[1], command="PRIVMSG", params=["baz", "hello friend singular"]
)
2018-12-28 18:43:12 +00:00
# should replay with the original server-time
# TODO this probably isn't testing anything because the timestamp only has second resolution,
# hence will typically match by accident
2021-02-22 18:02:13 +00:00
self.assertEqual(privmsgs[0].tags.get("time"), channelMsgTime)
2018-12-31 00:05:18 +00:00
2019-05-24 10:16:02 +00:00
# legacy client should receive a QUIT and a JOIN
2021-02-22 18:02:13 +00:00
quit, join = [m for m in self.getMessages(1) if m.command in ("QUIT", "JOIN")]
self.assertEqual(quit.command, "QUIT")
self.assertTrue(quit.prefix.startswith("baz"))
self.assertMessageEqual(join, command="JOIN", params=[chname])
self.assertTrue(join.prefix.startswith("baz"))
2019-05-24 10:16:02 +00:00
2018-12-31 00:05:18 +00:00
# original client should have been disconnected
self.assertDisconnected(2)
# new client should be receiving PRIVMSG sent to baz
2021-02-22 18:02:13 +00:00
self.sendLine(1, "PRIVMSG baz :hello again")
2018-12-31 00:05:18 +00:00
self.getMessages(1)
2021-02-22 18:02:13 +00:00
self.assertMessageEqual(
self.getMessage(4), command="PRIVMSG", params=["baz", "hello again"]
)
2019-05-24 10:16:02 +00:00
# test chain-resuming (resuming the resumed connection, using the new token)
self.addClient()
2021-02-22 18:02:13 +00:00
self.sendLine(5, "CAP LS")
self.sendLine(5, "CAP REQ :batch labeled-response server-time draft/resume-0.5")
self.sendLine(5, "NICK tempnick_")
self.sendLine(5, "USER tempuser 0 * tempuser")
self.sendLine(5, "RESUME " + new_token)
2019-05-24 10:16:02 +00:00
ms = self.getMessages(5)
2021-02-22 18:02:13 +00:00
resume_messages = [m for m in ms if m.command == "RESUME"]
2019-05-24 10:16:02 +00:00
self.assertEqual(len(resume_messages), 2)
2021-02-22 18:02:13 +00:00
self.assertEqual(resume_messages[0].params[0], "TOKEN")
2019-05-24 10:16:02 +00:00
new_new_token = resume_messages[0].params[1]
2021-02-22 18:02:13 +00:00
self.assertNotEqual(
token,
new_new_token,
"should receive a new, strong resume token; instead got " + new_new_token,
)
self.assertNotEqual(
new_token,
new_new_token,
"should receive a new, strong resume token; instead got " + new_new_token,
)
2019-05-24 10:16:02 +00:00
# success message
2021-02-22 18:02:13 +00:00
self.assertMessageEqual(
resume_messages[1], command="RESUME", params=["SUCCESS", "baz"]
)
2019-05-24 10:16:02 +00:00
2021-02-22 18:02:13 +00:00
@cases.SpecificationSelector.requiredBySpecification("Oragono")
2019-05-24 10:16:02 +00:00
def testBRB(self):
2021-02-22 18:02:13 +00:00
chname = "#" + secrets.token_hex(12)
self.connectClient(
"bar",
capabilities=[
"batch",
"labeled-response",
"message-tags",
"server-time",
"draft/resume-0.5",
],
)
2019-05-24 10:16:02 +00:00
ms = self.getMessages(1)
self.joinChannel(1, chname)
2019-05-24 10:16:02 +00:00
2021-02-22 18:02:13 +00:00
welcome = self.connectClient(
"baz",
capabilities=[
"batch",
"labeled-response",
"server-time",
"draft/resume-0.5",
],
)
resume_messages = [m for m in welcome if m.command == "RESUME"]
2019-05-24 10:16:02 +00:00
self.assertEqual(len(resume_messages), 1)
2021-02-22 18:02:13 +00:00
self.assertEqual(resume_messages[0].params[0], "TOKEN")
2019-05-24 10:16:02 +00:00
token = resume_messages[0].params[1]
self.joinChannel(2, chname)
2019-05-24 10:16:02 +00:00
self.getMessages(1)
2021-02-22 18:02:13 +00:00
self.sendLine(2, "BRB :software upgrade")
2019-05-24 10:16:02 +00:00
# should receive, e.g., `BRB 210` (number of seconds)
2021-02-22 18:02:13 +00:00
ms = [m for m in self.getMessages(2) if m.command == "BRB"]
2019-05-24 10:16:02 +00:00
self.assertEqual(len(ms), 1)
self.assertGreater(int(ms[0].params[0]), 1)
# BRB disconnects you
self.assertDisconnected(2)
# without sending a QUIT line to friends
self.assertEqual(self.getMessages(1), [])
2021-02-22 18:02:13 +00:00
self.sendLine(1, "PRIVMSG baz :hey there")
2019-05-24 10:16:02 +00:00
# BRB message should be sent as an away message
2021-02-22 18:02:13 +00:00
self.assertMessageEqual(
self.getMessage(1),
command=RPL_AWAY,
params=["bar", "baz", "software upgrade"],
)
2019-05-24 10:16:02 +00:00
self.addClient(3)
2021-02-22 18:02:13 +00:00
self.sendLine(3, "CAP REQ :batch account-tag message-tags draft/resume-0.5")
self.sendLine(3, " ".join(("RESUME", token, ANCIENT_TIMESTAMP)))
2019-05-24 10:16:02 +00:00
ms = self.getMessages(3)
2021-02-22 18:02:13 +00:00
resume_messages = [m for m in ms if m.command == "RESUME"]
2019-05-24 10:16:02 +00:00
self.assertEqual(len(resume_messages), 2)
2021-02-22 18:02:13 +00:00
self.assertEqual(resume_messages[0].params[0], "TOKEN")
self.assertMessageEqual(
resume_messages[1], command="RESUME", params=["SUCCESS", "baz"]
)
privmsgs = [
m for m in ms if m.command == "PRIVMSG" and m.prefix.startswith("bar")
]
2019-05-24 10:16:02 +00:00
self.assertEqual(len(privmsgs), 1)
2021-02-22 18:02:13 +00:00
self.assertMessageEqual(privmsgs[0], params=["baz", "hey there"])
2019-05-29 11:32:22 +00:00
# friend with the resume cap should receive a RESUMED message
2021-02-22 18:02:13 +00:00
resumed_messages = [m for m in self.getMessages(1) if m.command == "RESUMED"]
2019-05-29 11:32:22 +00:00
self.assertEqual(len(resumed_messages), 1)
2021-02-22 18:02:13 +00:00
self.assertTrue(resumed_messages[0].prefix.startswith("baz"))