From bf4fc8fb6c6789a97ceeb8ef9ade7c5df979ec00 Mon Sep 17 00:00:00 2001 From: Trevor Bergeron Date: Sat, 22 Oct 2022 20:51:08 -0400 Subject: [PATCH 1/6] coretasks: implement scram-sha-256 --- docs/source/configuration.rst | 2 +- pyproject.toml | 1 + sopel/config/core_section.py | 10 +++-- sopel/coretasks.py | 36 +++++++++++++++- test/test_coretasks.py | 77 +++++++++++++++++++++++++++++++++++ 5 files changed, 121 insertions(+), 5 deletions(-) diff --git a/docs/source/configuration.rst b/docs/source/configuration.rst index 07b873297..42de7254b 100644 --- a/docs/source/configuration.rst +++ b/docs/source/configuration.rst @@ -576,7 +576,7 @@ When :attr:`~CoreSection.server_auth_method` is defined the settings used are: * :attr:`~CoreSection.server_auth_username`: account's username * :attr:`~CoreSection.server_auth_password`: account's password * :attr:`~CoreSection.server_auth_sasl_mech`: the SASL mechanism to use - (defaults to ``PLAIN``; ``EXTERNAL`` is also available) + (default is ``PLAIN``; ``EXTERNAL`` and ``SCRAM-SHA-256`` are also available) For example, this will use NickServ ``IDENTIFY`` command and SASL mechanism:: diff --git a/pyproject.toml b/pyproject.toml index 5746b069a..aeb62999e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,6 +52,7 @@ dependencies = [ "importlib_metadata>=3.6", "packaging>=23.2", "sopel-help>=0.4.0", + "scramp>=1.4.4,<2", ] [project.urls] diff --git a/sopel/config/core_section.py b/sopel/config/core_section.py index 15778927c..70afcde2d 100644 --- a/sopel/config/core_section.py +++ b/sopel/config/core_section.py @@ -1191,12 +1191,16 @@ def homedir(self): :default: ``PLAIN`` - ``EXTERNAL`` is also supported, e.g. for using :attr:`client_cert_file` to - authenticate via CertFP. + Supported mechanisms are: + + * ``PLAIN``, to authenticate by sending a plaintext password + * ``EXTERNAL``, to authenticate using a TLS client certificate + (see :attr:`client_cert_file`) + * ``SCRAM-SHA-256``, for password-based challenge-response authentication .. versionadded:: 7.0 .. versionchanged:: 8.0 - Added support for SASL EXTERNAL mechanism. + Added support for SASL EXTERNAL and SCRAM-SHA-256 mechanisms. """ server_auth_username = ValidatedAttribute('server_auth_username') diff --git a/sopel/coretasks.py b/sopel/coretasks.py index 45e935154..9a129adef 100644 --- a/sopel/coretasks.py +++ b/sopel/coretasks.py @@ -32,6 +32,9 @@ import time from typing import Callable, Optional, TYPE_CHECKING +from scramp import ScramClient, ScramException +from scramp.core import ClientStage as ScramClientStage + from sopel import config, plugin from sopel.irc import isupport, utils from sopel.tools import events, jobs, SopelMemory, target @@ -1250,7 +1253,38 @@ def auth_proceed(bot, trigger): bot.write(('AUTHENTICATE', '*')) return - # TODO: Implement SCRAM challenges + elif mech == "SCRAM-SHA-256": + if trigger.args[0] == "+": + bot._scram_client = ScramClient([mech], sasl_username, sasl_password) + client_first = bot._scram_client.get_client_first() + LOGGER.info("Sending SASL SCRAM client first") + send_authenticate(bot, client_first) + elif bot._scram_client.stage == ScramClientStage.get_client_first: + server_first = base64.b64decode(trigger.args[0]).decode("utf-8") + bot._scram_client.set_server_first(server_first) + if bot._scram_client.iterations < 4096: + LOGGER.warning( + "SASL SCRAM iteration count is insecure, continuing anyway" + ) + elif bot._scram_client.iterations >= 4_000_000: + LOGGER.warning( + "SASL SCRAM iteration count is very high, this will be slow..." + ) + client_final = bot._scram_client.get_client_final() + LOGGER.info("Sending SASL SCRAM client final") + send_authenticate(bot, client_final) + elif bot._scram_client.stage == ScramClientStage.get_client_final: + server_final = base64.b64decode(trigger.args[0]).decode("utf-8") + try: + bot._scram_client.set_server_final(server_final) + except ScramException as e: + LOGGER.error("SASL SCRAM failed: %r", e) + bot.write(("AUTHENTICATE", "*")) + raise e + LOGGER.info("SASL SCRAM succeeded") + bot.write(("AUTHENTICATE", "+")) + bot._scram_client = None + return def _make_sasl_plain_token(account, password): diff --git a/test/test_coretasks.py b/test/test_coretasks.py index c01174e1b..51eb4eb62 100644 --- a/test/test_coretasks.py +++ b/test/test_coretasks.py @@ -1,10 +1,12 @@ """coretasks.py tests""" from __future__ import annotations +from base64 import b64decode, b64encode from datetime import datetime, timezone import logging import pytest +from scramp import ScramMechanism from sopel import coretasks from sopel.irc import isupport @@ -17,6 +19,7 @@ [core] owner = Uowner nick = TestBot +auth_password = hunter2 enable = coretasks """ @@ -502,6 +505,80 @@ def test_handle_rpl_myinfo(mockbot): assert mockbot.myinfo.version == 'example-1.2.3' +def test_sasl_plain_token_generation(): + """Make sure SASL PLAIN tokens match the expected format.""" + assert ( + coretasks._make_sasl_plain_token('sopel', 'sasliscool') == + 'sopel\x00sopel\x00sasliscool') + + +def test_sasl_plain_auth(mockbot): + """Verify the bot performs SASL PLAIN auth correctly.""" + mockbot.settings.core.auth_method = "sasl" + mockbot.settings.core.auth_target = "PLAIN" + mockbot.on_message("CAP TestBot ACK :sasl") + assert mockbot.backend.message_sent == rawlist("AUTHENTICATE PLAIN") + mockbot.on_message("AUTHENTICATE +") + assert mockbot.backend.message_sent == rawlist( + "AUTHENTICATE PLAIN", + "AUTHENTICATE VGVzdEJvdABUZXN0Qm90AGh1bnRlcjI=", + ) + mockbot.on_message( + "900 TestBot test!test@test TestBot :You are now logged in as TestBot" + ) + mockbot.on_message("903 TestBot :SASL authentication succeeded") + assert mockbot.backend.message_sent == rawlist( + "AUTHENTICATE PLAIN", + "AUTHENTICATE VGVzdEJvdABUZXN0Qm90AGh1bnRlcjI=", + "CAP END", + ) + + +def test_sasl_scram_sha_256_auth(mockbot): + """Verify the bot performs SASL SCRAM-SHA-256 auth correctly.""" + mech = ScramMechanism() + salt, stored_key, server_key, iter_count = mech.make_auth_info( + "hunter2", iteration_count=5000 + ) + scram_server = mech.make_server( + lambda x: (salt, stored_key, server_key, iter_count) + ) + + mockbot.settings.core.auth_method = "sasl" + mockbot.settings.core.auth_target = "SCRAM-SHA-256" + mockbot.on_message("CAP TestBot ACK :sasl") + assert mockbot.backend.message_sent == rawlist("AUTHENTICATE SCRAM-SHA-256") + mockbot.on_message("AUTHENTICATE +") + + scram_server.set_client_first( + b64decode(mockbot.backend.message_sent[-1].split(b" ")[-1]).decode("utf-8") + ) + mockbot.on_message( + "AUTHENTICATE " + + b64encode(scram_server.get_server_first().encode("utf-8")).decode("utf-8") + ) + scram_server.set_client_final( + b64decode(mockbot.backend.message_sent[-1].split(b" ")[-1]).decode("utf-8") + ) + mockbot.on_message( + "AUTHENTICATE " + + b64encode(scram_server.get_server_final().encode("utf-8")).decode("utf-8") + ) + assert ( + len(mockbot.backend.message_sent) == 4 + and mockbot.backend.message_sent[-1] == rawlist("AUTHENTICATE +")[0] + ) + + mockbot.on_message( + "900 TestBot test!test@test TestBot :You are now logged in as TestBot" + ) + mockbot.on_message("903 TestBot :SASL authentication succeeded") + assert ( + len(mockbot.backend.message_sent) == 5 + and mockbot.backend.message_sent[-1] == rawlist("CAP END")[0] + ) + + def test_recv_chghost(mockbot, ircfactory): """Ensure that CHGHOST messages are correctly handled.""" irc = ircfactory(mockbot) From c9d695841d01604a5907c437e75e4a4e33af514c Mon Sep 17 00:00:00 2001 From: Trevor Bergeron Date: Wed, 26 Oct 2022 13:31:54 -0400 Subject: [PATCH 2/6] more tests, handle KeyError/b64 --- sopel/coretasks.py | 18 ++++++++---- test/test_coretasks.py | 63 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 6 deletions(-) diff --git a/sopel/coretasks.py b/sopel/coretasks.py index 9a129adef..7d7dc0d17 100644 --- a/sopel/coretasks.py +++ b/sopel/coretasks.py @@ -23,6 +23,7 @@ from __future__ import annotations import base64 +from binascii import Error as BinasciiError import collections import copy from datetime import datetime, timedelta, timezone @@ -1260,8 +1261,13 @@ def auth_proceed(bot, trigger): LOGGER.info("Sending SASL SCRAM client first") send_authenticate(bot, client_first) elif bot._scram_client.stage == ScramClientStage.get_client_first: - server_first = base64.b64decode(trigger.args[0]).decode("utf-8") - bot._scram_client.set_server_first(server_first) + try: + server_first = base64.b64decode(trigger.args[0]).decode("utf-8") + bot._scram_client.set_server_first(server_first) + except (BinasciiError, KeyError, ScramException) as e: + LOGGER.error("SASL SCRAM server_first failed: %r", e) + bot.write(("AUTHENTICATE", "*")) + return if bot._scram_client.iterations < 4096: LOGGER.warning( "SASL SCRAM iteration count is insecure, continuing anyway" @@ -1274,13 +1280,13 @@ def auth_proceed(bot, trigger): LOGGER.info("Sending SASL SCRAM client final") send_authenticate(bot, client_final) elif bot._scram_client.stage == ScramClientStage.get_client_final: - server_final = base64.b64decode(trigger.args[0]).decode("utf-8") try: + server_final = base64.b64decode(trigger.args[0]).decode("utf-8") bot._scram_client.set_server_final(server_final) - except ScramException as e: - LOGGER.error("SASL SCRAM failed: %r", e) + except (BinasciiError, KeyError, ScramException) as e: + LOGGER.error("SASL SCRAM server_final failed: %r", e) bot.write(("AUTHENTICATE", "*")) - raise e + return LOGGER.info("SASL SCRAM succeeded") bot.write(("AUTHENTICATE", "+")) bot._scram_client = None diff --git a/test/test_coretasks.py b/test/test_coretasks.py index 51eb4eb62..d1ce35367 100644 --- a/test/test_coretasks.py +++ b/test/test_coretasks.py @@ -579,6 +579,69 @@ def test_sasl_scram_sha_256_auth(mockbot): ) +def test_sasl_scram_sha_256_nonsense_server_first(mockbot): + """Verify the bot handles a nonsense SCRAM-SHA-256 server_first correctly.""" + mech = ScramMechanism() + salt, stored_key, server_key, iter_count = mech.make_auth_info( + "hunter2", iteration_count=5000 + ) + scram_server = mech.make_server( + lambda x: (salt, stored_key, server_key, iter_count) + ) + + mockbot.settings.core.auth_method = "sasl" + mockbot.settings.core.auth_target = "SCRAM-SHA-256" + mockbot.on_message("CAP TestBot ACK :sasl") + mockbot.on_message("AUTHENTICATE +") + + scram_server.set_client_first( + b64decode(mockbot.backend.message_sent[-1].split(b" ")[-1]).decode("utf-8") + ) + mockbot.on_message( + "AUTHENTICATE " + + b64encode(b"j=unk").decode("utf-8") + ) + assert ( + len(mockbot.backend.message_sent) == 3 + and mockbot.backend.message_sent[-1] == rawlist("AUTHENTICATE *")[0] + ) + + +def test_sasl_scram_sha_256_nonsense_server_final(mockbot): + """Verify the bot handles a nonsense SCRAM-SHA-256 server_final correctly.""" + mech = ScramMechanism() + salt, stored_key, server_key, iter_count = mech.make_auth_info( + "hunter2", iteration_count=5000 + ) + scram_server = mech.make_server( + lambda x: (salt, stored_key, server_key, iter_count) + ) + + mockbot.settings.core.auth_method = "sasl" + mockbot.settings.core.auth_target = "SCRAM-SHA-256" + mockbot.on_message("CAP TestBot ACK :sasl") + mockbot.on_message("AUTHENTICATE +") + + scram_server.set_client_first( + b64decode(mockbot.backend.message_sent[-1].split(b" ")[-1]).decode("utf-8") + ) + mockbot.on_message( + "AUTHENTICATE " + + b64encode(scram_server.get_server_first().encode("utf-8")).decode("utf-8") + ) + scram_server.set_client_final( + b64decode(mockbot.backend.message_sent[-1].split(b" ")[-1]).decode("utf-8") + ) + mockbot.on_message( + "AUTHENTICATE " + + b64encode(b"j=unk").decode("utf-8") + ) + assert ( + len(mockbot.backend.message_sent) == 4 + and mockbot.backend.message_sent[-1] == rawlist("AUTHENTICATE *")[0] + ) + + def test_recv_chghost(mockbot, ircfactory): """Ensure that CHGHOST messages are correctly handled.""" irc = ircfactory(mockbot) From d9c0f35445261d1c8abe50641d0da155a1e4e3ec Mon Sep 17 00:00:00 2001 From: Trevor Bergeron Date: Wed, 26 Oct 2022 14:01:56 -0400 Subject: [PATCH 3/6] more tests, test tweaks --- test/test_coretasks.py | 51 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/test/test_coretasks.py b/test/test_coretasks.py index d1ce35367..02a12c3ac 100644 --- a/test/test_coretasks.py +++ b/test/test_coretasks.py @@ -597,10 +597,7 @@ def test_sasl_scram_sha_256_nonsense_server_first(mockbot): scram_server.set_client_first( b64decode(mockbot.backend.message_sent[-1].split(b" ")[-1]).decode("utf-8") ) - mockbot.on_message( - "AUTHENTICATE " - + b64encode(b"j=unk").decode("utf-8") - ) + mockbot.on_message("AUTHENTICATE " + b64encode(b"junk").decode("utf-8")) assert ( len(mockbot.backend.message_sent) == 3 and mockbot.backend.message_sent[-1] == rawlist("AUTHENTICATE *")[0] @@ -632,10 +629,54 @@ def test_sasl_scram_sha_256_nonsense_server_final(mockbot): scram_server.set_client_final( b64decode(mockbot.backend.message_sent[-1].split(b" ")[-1]).decode("utf-8") ) + mockbot.on_message("AUTHENTICATE " + b64encode(b"junk").decode("utf-8")) + assert ( + len(mockbot.backend.message_sent) == 4 + and mockbot.backend.message_sent[-1] == rawlist("AUTHENTICATE *")[0] + ) + + +def test_sasl_scram_sha_256_error_server_first(mockbot): + """Verify the bot handles an error SCRAM-SHA-256 server_first correctly.""" + + mockbot.settings.core.auth_method = "sasl" + mockbot.settings.core.auth_target = "SCRAM-SHA-256" + mockbot.on_message("CAP TestBot ACK :sasl") + mockbot.on_message("AUTHENTICATE +") + + mockbot.on_message("AUTHENTICATE " + b64encode(b"e=some-error").decode("utf-8")) + assert ( + len(mockbot.backend.message_sent) == 3 + and mockbot.backend.message_sent[-1] == rawlist("AUTHENTICATE *")[0] + ) + + +def test_sasl_scram_sha_256_error_server_final(mockbot): + """Verify the bot handles an error SCRAM-SHA-256 server_final correctly.""" + mech = ScramMechanism() + salt, stored_key, server_key, iter_count = mech.make_auth_info( + "hunter2", iteration_count=5000 + ) + scram_server = mech.make_server( + lambda x: (salt, stored_key, server_key, iter_count) + ) + + mockbot.settings.core.auth_method = "sasl" + mockbot.settings.core.auth_target = "SCRAM-SHA-256" + mockbot.on_message("CAP TestBot ACK :sasl") + mockbot.on_message("AUTHENTICATE +") + + scram_server.set_client_first( + b64decode(mockbot.backend.message_sent[-1].split(b" ")[-1]).decode("utf-8") + ) mockbot.on_message( "AUTHENTICATE " - + b64encode(b"j=unk").decode("utf-8") + + b64encode(scram_server.get_server_first().encode("utf-8")).decode("utf-8") + ) + scram_server.set_client_final( + b64decode(mockbot.backend.message_sent[-1].split(b" ")[-1]).decode("utf-8") ) + mockbot.on_message("AUTHENTICATE " + b64encode(b"e=some-error").decode("utf-8")) assert ( len(mockbot.backend.message_sent) == 4 and mockbot.backend.message_sent[-1] == rawlist("AUTHENTICATE *")[0] From 746e6147e8199df7f2bdb42c2287dc288f1138d9 Mon Sep 17 00:00:00 2001 From: Trevor Bergeron Date: Sun, 13 Aug 2023 10:58:25 -0400 Subject: [PATCH 4/6] squashme: fix tests I still want a "has the user configured a sasl method we don't support" check but that's been annoying to add --- sopel/coretasks.py | 8 +++++ test/test_coretasks.py | 70 ++++++++++++++++++++++++++++++------------ 2 files changed, 58 insertions(+), 20 deletions(-) diff --git a/sopel/coretasks.py b/sopel/coretasks.py index 7d7dc0d17..dbee7fec0 100644 --- a/sopel/coretasks.py +++ b/sopel/coretasks.py @@ -98,8 +98,10 @@ def _handle_sasl_capability( # Manage CAP REQ :sasl auth_method = bot.settings.core.auth_method server_auth_method = bot.settings.core.server_auth_method + auth_target = bot.settings.core.auth_target is_required = 'sasl' in (auth_method, server_auth_method) + LOGGER.critical("FOO. BAR!") if not is_required: # not required: we are fine, available or not return plugin.CapabilityNegotiation.DONE @@ -110,6 +112,12 @@ def _handle_sasl_capability( 'cannot authenticate with SASL.', ) return plugin.CapabilityNegotiation.ERROR + elif auth_target not in ["PLAIN", "EXTERNAL", "SCRAM-SHA-256"]: + LOGGER.error( + 'Configured SASL method %r is not supported by Sopel.', + auth_target, + ) + return plugin.CapabilityNegotiation.ERROR # Check SASL configuration (password is required) password, mech = _get_sasl_pass_and_mech(bot) diff --git a/test/test_coretasks.py b/test/test_coretasks.py index 02a12c3ac..8d0efae40 100644 --- a/test/test_coretasks.py +++ b/test/test_coretasks.py @@ -4,6 +4,7 @@ from base64 import b64decode, b64encode from datetime import datetime, timezone import logging +from typing import TYPE_CHECKING import pytest from scramp import ScramMechanism @@ -14,6 +15,9 @@ from sopel.tests import rawlist from sopel.tools import Identifier +if TYPE_CHECKING: + from sopel.bot import Sopel + TMP_CONFIG = """ [core] @@ -512,29 +516,47 @@ def test_sasl_plain_token_generation(): 'sopel\x00sopel\x00sasliscool') -def test_sasl_plain_auth(mockbot): +def test_sasl_bad_method(mockbot: Sopel): + """Verify the bot behaves when configured with an unsupported SASL method.""" + mockbot.settings.core.auth_method = "sasl" + mockbot.settings.core.auth_target = "SCRAM-MD4" + mockbot.on_message("CAP * LS :sasl") + mockbot.on_message("CAP TestBot ACK :sasl") + assert mockbot.backend.message_sent == rawlist( + "CAP REQ :sasl", + "CAP END", + ) + #with pytest.raises(Exception): + mockbot.on_message("AUTHENTICATE +") + + +def test_sasl_plain_auth(mockbot: Sopel): """Verify the bot performs SASL PLAIN auth correctly.""" mockbot.settings.core.auth_method = "sasl" mockbot.settings.core.auth_target = "PLAIN" + mockbot.on_message("CAP * LS :sasl") mockbot.on_message("CAP TestBot ACK :sasl") - assert mockbot.backend.message_sent == rawlist("AUTHENTICATE PLAIN") - mockbot.on_message("AUTHENTICATE +") assert mockbot.backend.message_sent == rawlist( + "CAP REQ :sasl", "AUTHENTICATE PLAIN", - "AUTHENTICATE VGVzdEJvdABUZXN0Qm90AGh1bnRlcjI=", + ) + mockbot.on_message("AUTHENTICATE +") + assert ( + len(mockbot.backend.message_sent) == 3 + and mockbot.backend.message_sent[-1] + == rawlist("AUTHENTICATE VGVzdEJvdABUZXN0Qm90AGh1bnRlcjI=")[0] ) mockbot.on_message( "900 TestBot test!test@test TestBot :You are now logged in as TestBot" ) mockbot.on_message("903 TestBot :SASL authentication succeeded") - assert mockbot.backend.message_sent == rawlist( - "AUTHENTICATE PLAIN", - "AUTHENTICATE VGVzdEJvdABUZXN0Qm90AGh1bnRlcjI=", - "CAP END", + assert ( + len(mockbot.backend.message_sent) == 4 + and mockbot.backend.message_sent[-1] == rawlist("CAP END")[0] ) -def test_sasl_scram_sha_256_auth(mockbot): +def test_sasl_scram_sha_256_auth(mockbot: Sopel): """Verify the bot performs SASL SCRAM-SHA-256 auth correctly.""" mech = ScramMechanism() salt, stored_key, server_key, iter_count = mech.make_auth_info( @@ -546,8 +568,12 @@ def test_sasl_scram_sha_256_auth(mockbot): mockbot.settings.core.auth_method = "sasl" mockbot.settings.core.auth_target = "SCRAM-SHA-256" + mockbot.on_message("CAP * LS :sasl") mockbot.on_message("CAP TestBot ACK :sasl") - assert mockbot.backend.message_sent == rawlist("AUTHENTICATE SCRAM-SHA-256") + assert mockbot.backend.message_sent == rawlist( + "CAP REQ :sasl", + "AUTHENTICATE SCRAM-SHA-256", + ) mockbot.on_message("AUTHENTICATE +") scram_server.set_client_first( @@ -565,7 +591,7 @@ def test_sasl_scram_sha_256_auth(mockbot): + b64encode(scram_server.get_server_final().encode("utf-8")).decode("utf-8") ) assert ( - len(mockbot.backend.message_sent) == 4 + len(mockbot.backend.message_sent) == 5 and mockbot.backend.message_sent[-1] == rawlist("AUTHENTICATE +")[0] ) @@ -574,12 +600,12 @@ def test_sasl_scram_sha_256_auth(mockbot): ) mockbot.on_message("903 TestBot :SASL authentication succeeded") assert ( - len(mockbot.backend.message_sent) == 5 + len(mockbot.backend.message_sent) == 6 and mockbot.backend.message_sent[-1] == rawlist("CAP END")[0] ) -def test_sasl_scram_sha_256_nonsense_server_first(mockbot): +def test_sasl_scram_sha_256_nonsense_server_first(mockbot: Sopel): """Verify the bot handles a nonsense SCRAM-SHA-256 server_first correctly.""" mech = ScramMechanism() salt, stored_key, server_key, iter_count = mech.make_auth_info( @@ -591,6 +617,7 @@ def test_sasl_scram_sha_256_nonsense_server_first(mockbot): mockbot.settings.core.auth_method = "sasl" mockbot.settings.core.auth_target = "SCRAM-SHA-256" + mockbot.on_message("CAP * LS :sasl") mockbot.on_message("CAP TestBot ACK :sasl") mockbot.on_message("AUTHENTICATE +") @@ -599,12 +626,12 @@ def test_sasl_scram_sha_256_nonsense_server_first(mockbot): ) mockbot.on_message("AUTHENTICATE " + b64encode(b"junk").decode("utf-8")) assert ( - len(mockbot.backend.message_sent) == 3 + len(mockbot.backend.message_sent) == 4 and mockbot.backend.message_sent[-1] == rawlist("AUTHENTICATE *")[0] ) -def test_sasl_scram_sha_256_nonsense_server_final(mockbot): +def test_sasl_scram_sha_256_nonsense_server_final(mockbot: Sopel): """Verify the bot handles a nonsense SCRAM-SHA-256 server_final correctly.""" mech = ScramMechanism() salt, stored_key, server_key, iter_count = mech.make_auth_info( @@ -616,6 +643,7 @@ def test_sasl_scram_sha_256_nonsense_server_final(mockbot): mockbot.settings.core.auth_method = "sasl" mockbot.settings.core.auth_target = "SCRAM-SHA-256" + mockbot.on_message("CAP * LS :sasl") mockbot.on_message("CAP TestBot ACK :sasl") mockbot.on_message("AUTHENTICATE +") @@ -631,27 +659,28 @@ def test_sasl_scram_sha_256_nonsense_server_final(mockbot): ) mockbot.on_message("AUTHENTICATE " + b64encode(b"junk").decode("utf-8")) assert ( - len(mockbot.backend.message_sent) == 4 + len(mockbot.backend.message_sent) == 5 and mockbot.backend.message_sent[-1] == rawlist("AUTHENTICATE *")[0] ) -def test_sasl_scram_sha_256_error_server_first(mockbot): +def test_sasl_scram_sha_256_error_server_first(mockbot: Sopel): """Verify the bot handles an error SCRAM-SHA-256 server_first correctly.""" mockbot.settings.core.auth_method = "sasl" mockbot.settings.core.auth_target = "SCRAM-SHA-256" + mockbot.on_message("CAP * LS :sasl") mockbot.on_message("CAP TestBot ACK :sasl") mockbot.on_message("AUTHENTICATE +") mockbot.on_message("AUTHENTICATE " + b64encode(b"e=some-error").decode("utf-8")) assert ( - len(mockbot.backend.message_sent) == 3 + len(mockbot.backend.message_sent) == 4 and mockbot.backend.message_sent[-1] == rawlist("AUTHENTICATE *")[0] ) -def test_sasl_scram_sha_256_error_server_final(mockbot): +def test_sasl_scram_sha_256_error_server_final(mockbot: Sopel): """Verify the bot handles an error SCRAM-SHA-256 server_final correctly.""" mech = ScramMechanism() salt, stored_key, server_key, iter_count = mech.make_auth_info( @@ -663,6 +692,7 @@ def test_sasl_scram_sha_256_error_server_final(mockbot): mockbot.settings.core.auth_method = "sasl" mockbot.settings.core.auth_target = "SCRAM-SHA-256" + mockbot.on_message("CAP * LS :sasl") mockbot.on_message("CAP TestBot ACK :sasl") mockbot.on_message("AUTHENTICATE +") @@ -678,7 +708,7 @@ def test_sasl_scram_sha_256_error_server_final(mockbot): ) mockbot.on_message("AUTHENTICATE " + b64encode(b"e=some-error").decode("utf-8")) assert ( - len(mockbot.backend.message_sent) == 4 + len(mockbot.backend.message_sent) == 5 and mockbot.backend.message_sent[-1] == rawlist("AUTHENTICATE *")[0] ) From 69812a6794f227b0b2455c148965c379ec0e81f1 Mon Sep 17 00:00:00 2001 From: Trevor Bergeron Date: Sat, 11 Nov 2023 21:15:48 -0500 Subject: [PATCH 5/6] squashme: move tests, address comments --- Makefile | 2 +- pyproject.toml | 9 ++ setup.cfg | 4 - sopel/coretasks.py | 26 ++-- test/coretasks/test_coretasks_sasl.py | 207 +++++++++++++++++++++++++ test/test_coretasks.py | 210 -------------------------- 6 files changed, 231 insertions(+), 227 deletions(-) diff --git a/Makefile b/Makefile index 35decee8c..a2c10f3c4 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ lint-style: flake8 lint-type: - mypy --check-untyped-defs sopel + mypy sopel .PHONY: test test_norecord test_novcr vcr_rerecord test: diff --git a/pyproject.toml b/pyproject.toml index aeb62999e..d8cf3e93b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,6 +73,15 @@ sopel-plugins = "sopel.cli.plugins:main" [project.entry-points.pytest11] pytest-sopel = "sopel.tests.pytest_plugin" +[tool.mypy] +check_untyped_defs = true +plugins = "sqlalchemy.ext.mypy.plugin" +show_error_codes = true + +[[tool.mypy.overrides]] +module = 'scramp.*' +ignore_missing_imports = true + [tool.pytest.ini_options] # NOTE: sopel/ is included here to include dynamically-generated tests testpaths = ["test", "sopel"] diff --git a/setup.cfg b/setup.cfg index 9d5eb9c20..bf53c04a3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -26,7 +26,3 @@ exclude = contrib/*, conftest.py no-accept-encodings = True - -[mypy] -plugins = sqlalchemy.ext.mypy.plugin -show_error_codes = True diff --git a/sopel/coretasks.py b/sopel/coretasks.py index dbee7fec0..22f7fe1fd 100644 --- a/sopel/coretasks.py +++ b/sopel/coretasks.py @@ -98,10 +98,8 @@ def _handle_sasl_capability( # Manage CAP REQ :sasl auth_method = bot.settings.core.auth_method server_auth_method = bot.settings.core.server_auth_method - auth_target = bot.settings.core.auth_target is_required = 'sasl' in (auth_method, server_auth_method) - LOGGER.critical("FOO. BAR!") if not is_required: # not required: we are fine, available or not return plugin.CapabilityNegotiation.DONE @@ -112,13 +110,6 @@ def _handle_sasl_capability( 'cannot authenticate with SASL.', ) return plugin.CapabilityNegotiation.ERROR - elif auth_target not in ["PLAIN", "EXTERNAL", "SCRAM-SHA-256"]: - LOGGER.error( - 'Configured SASL method %r is not supported by Sopel.', - auth_target, - ) - return plugin.CapabilityNegotiation.ERROR - # Check SASL configuration (password is required) password, mech = _get_sasl_pass_and_mech(bot) if not password: @@ -130,9 +121,18 @@ def _handle_sasl_capability( cap_info = bot.capabilities.get_capability_info('sasl') cap_params = cap_info.params - available_mechs = cap_params.split(',') if cap_params else [] + server_mechs = cap_params.split(',') if cap_params else [] - if available_mechs and mech not in available_mechs: + sopel_mechs = ["PLAIN", "EXTERNAL", "SCRAM-SHA-256"] + if mech not in sopel_mechs: + raise config.ConfigurationError( + 'SASL mechanism "{mech}" is not supported by Sopel; ' + 'available mechanisms are: {available}.'.format( + mech=mech, + available=', '.join(sopel_mechs), + ) + ) + if server_mechs and mech not in server_mechs: # Raise an error if configured to use an unsupported SASL mechanism, # but only if the server actually advertised supported mechanisms, # i.e. this network supports SASL 3.2 @@ -141,11 +141,13 @@ def _handle_sasl_capability( # by the sasl_mechs() function # See https://github.com/sopel-irc/sopel/issues/1780 for background + + common_mechs = set(sopel_mechs) & set(server_mechs) raise config.ConfigurationError( 'SASL mechanism "{mech}" is not advertised by this server; ' 'available mechanisms are: {available}.'.format( mech=mech, - available=', '.join(available_mechs), + available=', '.join(common_mechs), ) ) diff --git a/test/coretasks/test_coretasks_sasl.py b/test/coretasks/test_coretasks_sasl.py index 39da87b22..19030e35a 100644 --- a/test/coretasks/test_coretasks_sasl.py +++ b/test/coretasks/test_coretasks_sasl.py @@ -1,14 +1,18 @@ """Test behavior of SASL by ``sopel.coretasks``""" from __future__ import annotations +from base64 import b64decode, b64encode +from logging import ERROR from typing import TYPE_CHECKING import pytest +from scramp import ScramMechanism from sopel import coretasks from sopel.tests import rawlist if TYPE_CHECKING: + from sopel.bot import Sopel from sopel.config import Config from sopel.tests.factories import BotFactory, ConfigFactory @@ -65,6 +69,11 @@ def tmpconfig(configfactory: ConfigFactory) -> Config: return configfactory('conf.ini', TMP_CONFIG_SASL_DEFAULT) +@pytest.fixture +def mockbot(tmpconfig, botfactory): + return botfactory.preloaded(tmpconfig) + + def test_sasl_plain_token_generation() -> None: """Make sure SASL PLAIN tokens match the expected format.""" assert ( @@ -344,3 +353,201 @@ def test_sasl_nak(botfactory: BotFactory, tmpconfig) -> None: 'CAP END', 'QUIT :Error negotiating capabilities.', ) + + +def test_sasl_bad_method(mockbot: Sopel, caplog: pytest.LogCaptureFixture): + """Verify the bot behaves when configured with an unsupported SASL method.""" + mockbot.settings.core.auth_method = "sasl" + mockbot.settings.core.auth_target = "SCRAM-MD4" + mockbot.on_message("CAP * LS :sasl") + mockbot.on_message("CAP TestBot ACK :sasl") + assert mockbot.backend.message_sent == rawlist( + "CAP REQ :sasl", + "CAP END", + ) + with caplog.at_level(ERROR): + mockbot.on_message("AUTHENTICATE +") + assert '"SCRAM-MD4" is not supported' in caplog.text + + +def test_sasl_plain_auth(mockbot: Sopel): + """Verify the bot performs SASL PLAIN auth correctly.""" + mockbot.settings.core.auth_method = "sasl" + mockbot.settings.core.auth_target = "PLAIN" + mockbot.on_message("CAP * LS :sasl") + mockbot.on_message("CAP TestBot ACK :sasl") + assert mockbot.backend.message_sent == rawlist( + "CAP REQ :sasl", + "AUTHENTICATE PLAIN", + ) + mockbot.on_message("AUTHENTICATE +") + assert ( + len(mockbot.backend.message_sent) == 3 + and mockbot.backend.message_sent[-1] + == rawlist("AUTHENTICATE VGVzdEJvdABUZXN0Qm90AHNlY3JldA==")[0] + ) + mockbot.on_message( + "900 TestBot test!test@test TestBot :You are now logged in as TestBot" + ) + mockbot.on_message("903 TestBot :SASL authentication succeeded") + assert ( + len(mockbot.backend.message_sent) == 4 + and mockbot.backend.message_sent[-1] == rawlist("CAP END")[0] + ) + + +def test_sasl_scram_sha_256_auth(mockbot: Sopel): + """Verify the bot performs SASL SCRAM-SHA-256 auth correctly.""" + mech = ScramMechanism() + salt, stored_key, server_key, iter_count = mech.make_auth_info( + "secret", iteration_count=5000 + ) + scram_server = mech.make_server( + lambda x: (salt, stored_key, server_key, iter_count) + ) + + mockbot.settings.core.auth_method = "sasl" + mockbot.settings.core.auth_target = "SCRAM-SHA-256" + mockbot.on_message("CAP * LS :sasl") + mockbot.on_message("CAP TestBot ACK :sasl") + assert mockbot.backend.message_sent == rawlist( + "CAP REQ :sasl", + "AUTHENTICATE SCRAM-SHA-256", + ) + mockbot.on_message("AUTHENTICATE +") + + scram_server.set_client_first( + b64decode(mockbot.backend.message_sent[-1].split(b" ")[-1]).decode("utf-8") + ) + mockbot.on_message( + "AUTHENTICATE " + + b64encode(scram_server.get_server_first().encode("utf-8")).decode("utf-8") + ) + scram_server.set_client_final( + b64decode(mockbot.backend.message_sent[-1].split(b" ")[-1]).decode("utf-8") + ) + mockbot.on_message( + "AUTHENTICATE " + + b64encode(scram_server.get_server_final().encode("utf-8")).decode("utf-8") + ) + assert ( + len(mockbot.backend.message_sent) == 5 + and mockbot.backend.message_sent[-1] == rawlist("AUTHENTICATE +")[0] + ) + + mockbot.on_message( + "900 TestBot test!test@test TestBot :You are now logged in as TestBot" + ) + mockbot.on_message("903 TestBot :SASL authentication succeeded") + assert ( + len(mockbot.backend.message_sent) == 6 + and mockbot.backend.message_sent[-1] == rawlist("CAP END")[0] + ) + + +def test_sasl_scram_sha_256_nonsense_server_first(mockbot: Sopel): + """Verify the bot handles a nonsense SCRAM-SHA-256 server_first correctly.""" + mech = ScramMechanism() + salt, stored_key, server_key, iter_count = mech.make_auth_info( + "secret", iteration_count=5000 + ) + scram_server = mech.make_server( + lambda x: (salt, stored_key, server_key, iter_count) + ) + + mockbot.settings.core.auth_method = "sasl" + mockbot.settings.core.auth_target = "SCRAM-SHA-256" + mockbot.on_message("CAP * LS :sasl") + mockbot.on_message("CAP TestBot ACK :sasl") + mockbot.on_message("AUTHENTICATE +") + + scram_server.set_client_first( + b64decode(mockbot.backend.message_sent[-1].split(b" ")[-1]).decode("utf-8") + ) + mockbot.on_message("AUTHENTICATE " + b64encode(b"junk").decode("utf-8")) + assert ( + len(mockbot.backend.message_sent) == 4 + and mockbot.backend.message_sent[-1] == rawlist("AUTHENTICATE *")[0] + ) + + +def test_sasl_scram_sha_256_nonsense_server_final(mockbot: Sopel): + """Verify the bot handles a nonsense SCRAM-SHA-256 server_final correctly.""" + mech = ScramMechanism() + salt, stored_key, server_key, iter_count = mech.make_auth_info( + "secret", iteration_count=5000 + ) + scram_server = mech.make_server( + lambda x: (salt, stored_key, server_key, iter_count) + ) + + mockbot.settings.core.auth_method = "sasl" + mockbot.settings.core.auth_target = "SCRAM-SHA-256" + mockbot.on_message("CAP * LS :sasl") + mockbot.on_message("CAP TestBot ACK :sasl") + mockbot.on_message("AUTHENTICATE +") + + scram_server.set_client_first( + b64decode(mockbot.backend.message_sent[-1].split(b" ")[-1]).decode("utf-8") + ) + mockbot.on_message( + "AUTHENTICATE " + + b64encode(scram_server.get_server_first().encode("utf-8")).decode("utf-8") + ) + scram_server.set_client_final( + b64decode(mockbot.backend.message_sent[-1].split(b" ")[-1]).decode("utf-8") + ) + mockbot.on_message("AUTHENTICATE " + b64encode(b"junk").decode("utf-8")) + assert ( + len(mockbot.backend.message_sent) == 5 + and mockbot.backend.message_sent[-1] == rawlist("AUTHENTICATE *")[0] + ) + + +def test_sasl_scram_sha_256_error_server_first(mockbot: Sopel): + """Verify the bot handles an error SCRAM-SHA-256 server_first correctly.""" + + mockbot.settings.core.auth_method = "sasl" + mockbot.settings.core.auth_target = "SCRAM-SHA-256" + mockbot.on_message("CAP * LS :sasl") + mockbot.on_message("CAP TestBot ACK :sasl") + mockbot.on_message("AUTHENTICATE +") + + mockbot.on_message("AUTHENTICATE " + b64encode(b"e=some-error").decode("utf-8")) + assert ( + len(mockbot.backend.message_sent) == 4 + and mockbot.backend.message_sent[-1] == rawlist("AUTHENTICATE *")[0] + ) + + +def test_sasl_scram_sha_256_error_server_final(mockbot: Sopel): + """Verify the bot handles an error SCRAM-SHA-256 server_final correctly.""" + mech = ScramMechanism() + salt, stored_key, server_key, iter_count = mech.make_auth_info( + "secret", iteration_count=5000 + ) + scram_server = mech.make_server( + lambda x: (salt, stored_key, server_key, iter_count) + ) + + mockbot.settings.core.auth_method = "sasl" + mockbot.settings.core.auth_target = "SCRAM-SHA-256" + mockbot.on_message("CAP * LS :sasl") + mockbot.on_message("CAP TestBot ACK :sasl") + mockbot.on_message("AUTHENTICATE +") + + scram_server.set_client_first( + b64decode(mockbot.backend.message_sent[-1].split(b" ")[-1]).decode("utf-8") + ) + mockbot.on_message( + "AUTHENTICATE " + + b64encode(scram_server.get_server_first().encode("utf-8")).decode("utf-8") + ) + scram_server.set_client_final( + b64decode(mockbot.backend.message_sent[-1].split(b" ")[-1]).decode("utf-8") + ) + mockbot.on_message("AUTHENTICATE " + b64encode(b"e=some-error").decode("utf-8")) + assert ( + len(mockbot.backend.message_sent) == 5 + and mockbot.backend.message_sent[-1] == rawlist("AUTHENTICATE *")[0] + ) diff --git a/test/test_coretasks.py b/test/test_coretasks.py index 8d0efae40..0680309fb 100644 --- a/test/test_coretasks.py +++ b/test/test_coretasks.py @@ -1,13 +1,10 @@ """coretasks.py tests""" from __future__ import annotations -from base64 import b64decode, b64encode from datetime import datetime, timezone import logging -from typing import TYPE_CHECKING import pytest -from scramp import ScramMechanism from sopel import coretasks from sopel.irc import isupport @@ -15,9 +12,6 @@ from sopel.tests import rawlist from sopel.tools import Identifier -if TYPE_CHECKING: - from sopel.bot import Sopel - TMP_CONFIG = """ [core] @@ -509,210 +503,6 @@ def test_handle_rpl_myinfo(mockbot): assert mockbot.myinfo.version == 'example-1.2.3' -def test_sasl_plain_token_generation(): - """Make sure SASL PLAIN tokens match the expected format.""" - assert ( - coretasks._make_sasl_plain_token('sopel', 'sasliscool') == - 'sopel\x00sopel\x00sasliscool') - - -def test_sasl_bad_method(mockbot: Sopel): - """Verify the bot behaves when configured with an unsupported SASL method.""" - mockbot.settings.core.auth_method = "sasl" - mockbot.settings.core.auth_target = "SCRAM-MD4" - mockbot.on_message("CAP * LS :sasl") - mockbot.on_message("CAP TestBot ACK :sasl") - assert mockbot.backend.message_sent == rawlist( - "CAP REQ :sasl", - "CAP END", - ) - #with pytest.raises(Exception): - mockbot.on_message("AUTHENTICATE +") - - -def test_sasl_plain_auth(mockbot: Sopel): - """Verify the bot performs SASL PLAIN auth correctly.""" - mockbot.settings.core.auth_method = "sasl" - mockbot.settings.core.auth_target = "PLAIN" - mockbot.on_message("CAP * LS :sasl") - mockbot.on_message("CAP TestBot ACK :sasl") - assert mockbot.backend.message_sent == rawlist( - "CAP REQ :sasl", - "AUTHENTICATE PLAIN", - ) - mockbot.on_message("AUTHENTICATE +") - assert ( - len(mockbot.backend.message_sent) == 3 - and mockbot.backend.message_sent[-1] - == rawlist("AUTHENTICATE VGVzdEJvdABUZXN0Qm90AGh1bnRlcjI=")[0] - ) - mockbot.on_message( - "900 TestBot test!test@test TestBot :You are now logged in as TestBot" - ) - mockbot.on_message("903 TestBot :SASL authentication succeeded") - assert ( - len(mockbot.backend.message_sent) == 4 - and mockbot.backend.message_sent[-1] == rawlist("CAP END")[0] - ) - - -def test_sasl_scram_sha_256_auth(mockbot: Sopel): - """Verify the bot performs SASL SCRAM-SHA-256 auth correctly.""" - mech = ScramMechanism() - salt, stored_key, server_key, iter_count = mech.make_auth_info( - "hunter2", iteration_count=5000 - ) - scram_server = mech.make_server( - lambda x: (salt, stored_key, server_key, iter_count) - ) - - mockbot.settings.core.auth_method = "sasl" - mockbot.settings.core.auth_target = "SCRAM-SHA-256" - mockbot.on_message("CAP * LS :sasl") - mockbot.on_message("CAP TestBot ACK :sasl") - assert mockbot.backend.message_sent == rawlist( - "CAP REQ :sasl", - "AUTHENTICATE SCRAM-SHA-256", - ) - mockbot.on_message("AUTHENTICATE +") - - scram_server.set_client_first( - b64decode(mockbot.backend.message_sent[-1].split(b" ")[-1]).decode("utf-8") - ) - mockbot.on_message( - "AUTHENTICATE " - + b64encode(scram_server.get_server_first().encode("utf-8")).decode("utf-8") - ) - scram_server.set_client_final( - b64decode(mockbot.backend.message_sent[-1].split(b" ")[-1]).decode("utf-8") - ) - mockbot.on_message( - "AUTHENTICATE " - + b64encode(scram_server.get_server_final().encode("utf-8")).decode("utf-8") - ) - assert ( - len(mockbot.backend.message_sent) == 5 - and mockbot.backend.message_sent[-1] == rawlist("AUTHENTICATE +")[0] - ) - - mockbot.on_message( - "900 TestBot test!test@test TestBot :You are now logged in as TestBot" - ) - mockbot.on_message("903 TestBot :SASL authentication succeeded") - assert ( - len(mockbot.backend.message_sent) == 6 - and mockbot.backend.message_sent[-1] == rawlist("CAP END")[0] - ) - - -def test_sasl_scram_sha_256_nonsense_server_first(mockbot: Sopel): - """Verify the bot handles a nonsense SCRAM-SHA-256 server_first correctly.""" - mech = ScramMechanism() - salt, stored_key, server_key, iter_count = mech.make_auth_info( - "hunter2", iteration_count=5000 - ) - scram_server = mech.make_server( - lambda x: (salt, stored_key, server_key, iter_count) - ) - - mockbot.settings.core.auth_method = "sasl" - mockbot.settings.core.auth_target = "SCRAM-SHA-256" - mockbot.on_message("CAP * LS :sasl") - mockbot.on_message("CAP TestBot ACK :sasl") - mockbot.on_message("AUTHENTICATE +") - - scram_server.set_client_first( - b64decode(mockbot.backend.message_sent[-1].split(b" ")[-1]).decode("utf-8") - ) - mockbot.on_message("AUTHENTICATE " + b64encode(b"junk").decode("utf-8")) - assert ( - len(mockbot.backend.message_sent) == 4 - and mockbot.backend.message_sent[-1] == rawlist("AUTHENTICATE *")[0] - ) - - -def test_sasl_scram_sha_256_nonsense_server_final(mockbot: Sopel): - """Verify the bot handles a nonsense SCRAM-SHA-256 server_final correctly.""" - mech = ScramMechanism() - salt, stored_key, server_key, iter_count = mech.make_auth_info( - "hunter2", iteration_count=5000 - ) - scram_server = mech.make_server( - lambda x: (salt, stored_key, server_key, iter_count) - ) - - mockbot.settings.core.auth_method = "sasl" - mockbot.settings.core.auth_target = "SCRAM-SHA-256" - mockbot.on_message("CAP * LS :sasl") - mockbot.on_message("CAP TestBot ACK :sasl") - mockbot.on_message("AUTHENTICATE +") - - scram_server.set_client_first( - b64decode(mockbot.backend.message_sent[-1].split(b" ")[-1]).decode("utf-8") - ) - mockbot.on_message( - "AUTHENTICATE " - + b64encode(scram_server.get_server_first().encode("utf-8")).decode("utf-8") - ) - scram_server.set_client_final( - b64decode(mockbot.backend.message_sent[-1].split(b" ")[-1]).decode("utf-8") - ) - mockbot.on_message("AUTHENTICATE " + b64encode(b"junk").decode("utf-8")) - assert ( - len(mockbot.backend.message_sent) == 5 - and mockbot.backend.message_sent[-1] == rawlist("AUTHENTICATE *")[0] - ) - - -def test_sasl_scram_sha_256_error_server_first(mockbot: Sopel): - """Verify the bot handles an error SCRAM-SHA-256 server_first correctly.""" - - mockbot.settings.core.auth_method = "sasl" - mockbot.settings.core.auth_target = "SCRAM-SHA-256" - mockbot.on_message("CAP * LS :sasl") - mockbot.on_message("CAP TestBot ACK :sasl") - mockbot.on_message("AUTHENTICATE +") - - mockbot.on_message("AUTHENTICATE " + b64encode(b"e=some-error").decode("utf-8")) - assert ( - len(mockbot.backend.message_sent) == 4 - and mockbot.backend.message_sent[-1] == rawlist("AUTHENTICATE *")[0] - ) - - -def test_sasl_scram_sha_256_error_server_final(mockbot: Sopel): - """Verify the bot handles an error SCRAM-SHA-256 server_final correctly.""" - mech = ScramMechanism() - salt, stored_key, server_key, iter_count = mech.make_auth_info( - "hunter2", iteration_count=5000 - ) - scram_server = mech.make_server( - lambda x: (salt, stored_key, server_key, iter_count) - ) - - mockbot.settings.core.auth_method = "sasl" - mockbot.settings.core.auth_target = "SCRAM-SHA-256" - mockbot.on_message("CAP * LS :sasl") - mockbot.on_message("CAP TestBot ACK :sasl") - mockbot.on_message("AUTHENTICATE +") - - scram_server.set_client_first( - b64decode(mockbot.backend.message_sent[-1].split(b" ")[-1]).decode("utf-8") - ) - mockbot.on_message( - "AUTHENTICATE " - + b64encode(scram_server.get_server_first().encode("utf-8")).decode("utf-8") - ) - scram_server.set_client_final( - b64decode(mockbot.backend.message_sent[-1].split(b" ")[-1]).decode("utf-8") - ) - mockbot.on_message("AUTHENTICATE " + b64encode(b"e=some-error").decode("utf-8")) - assert ( - len(mockbot.backend.message_sent) == 5 - and mockbot.backend.message_sent[-1] == rawlist("AUTHENTICATE *")[0] - ) - - def test_recv_chghost(mockbot, ircfactory): """Ensure that CHGHOST messages are correctly handled.""" irc = ircfactory(mockbot) From 3aa7f3754070749127ecdc4dda353a54dee6860b Mon Sep 17 00:00:00 2001 From: Trevor Bergeron Date: Sun, 12 Nov 2023 22:46:30 -0500 Subject: [PATCH 6/6] squashme comments [skip ci] --- pyproject.toml | 1 + test/coretasks/test_coretasks_sasl.py | 9 ++++----- test/test_coretasks.py | 1 - 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d8cf3e93b..39296191d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -78,6 +78,7 @@ check_untyped_defs = true plugins = "sqlalchemy.ext.mypy.plugin" show_error_codes = true +# Remove once scramp has type stubs or annotations [[tool.mypy.overrides]] module = 'scramp.*' ignore_missing_imports = true diff --git a/test/coretasks/test_coretasks_sasl.py b/test/coretasks/test_coretasks_sasl.py index 19030e35a..9819a50e1 100644 --- a/test/coretasks/test_coretasks_sasl.py +++ b/test/coretasks/test_coretasks_sasl.py @@ -445,8 +445,8 @@ def test_sasl_scram_sha_256_auth(mockbot: Sopel): ) -def test_sasl_scram_sha_256_nonsense_server_first(mockbot: Sopel): - """Verify the bot handles a nonsense SCRAM-SHA-256 server_first correctly.""" +def test_sasl_scram_sha_256_invalid_server_first(mockbot: Sopel): + """Verify the bot handles an invalid SCRAM-SHA-256 server_first correctly.""" mech = ScramMechanism() salt, stored_key, server_key, iter_count = mech.make_auth_info( "secret", iteration_count=5000 @@ -471,8 +471,8 @@ def test_sasl_scram_sha_256_nonsense_server_first(mockbot: Sopel): ) -def test_sasl_scram_sha_256_nonsense_server_final(mockbot: Sopel): - """Verify the bot handles a nonsense SCRAM-SHA-256 server_final correctly.""" +def test_sasl_scram_sha_256_invalid_server_final(mockbot: Sopel): + """Verify the bot handles an invalid SCRAM-SHA-256 server_final correctly.""" mech = ScramMechanism() salt, stored_key, server_key, iter_count = mech.make_auth_info( "secret", iteration_count=5000 @@ -506,7 +506,6 @@ def test_sasl_scram_sha_256_nonsense_server_final(mockbot: Sopel): def test_sasl_scram_sha_256_error_server_first(mockbot: Sopel): """Verify the bot handles an error SCRAM-SHA-256 server_first correctly.""" - mockbot.settings.core.auth_method = "sasl" mockbot.settings.core.auth_target = "SCRAM-SHA-256" mockbot.on_message("CAP * LS :sasl") diff --git a/test/test_coretasks.py b/test/test_coretasks.py index 0680309fb..c01174e1b 100644 --- a/test/test_coretasks.py +++ b/test/test_coretasks.py @@ -17,7 +17,6 @@ [core] owner = Uowner nick = TestBot -auth_password = hunter2 enable = coretasks """