Skip to content

Commit

Permalink
Merge pull request #2561 from half-duplex/sasl-external-nopw
Browse files Browse the repository at this point in the history
coretasks: fix SASL EXTERNAL without password set
  • Loading branch information
dgw authored Nov 25, 2023
2 parents 3ff58c1 + 6b59e3a commit 7b03d9c
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 2 deletions.
4 changes: 2 additions & 2 deletions sopel/coretasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,9 @@ def _handle_sasl_capability(
)
return plugin.CapabilityNegotiation.ERROR

# Check SASL configuration (password is required)
# Check SASL configuration (password is required for PLAIN/SCRAM)
password, mech = _get_sasl_pass_and_mech(bot)
if not password:
if mech != "EXTERNAL" and not password:
raise config.ConfigurationError(
'SASL authentication required but no password available; '
'please check your configuration file.',
Expand Down
70 changes: 70 additions & 0 deletions test/coretasks/test_coretasks_sasl.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,15 @@
auth_method = sasl
"""

TMP_CONFIG_SASL_EXTERNAL = """
[core]
owner = Uowner
nick = TestBot
enable = coretasks
auth_method = sasl
auth_target = EXTERNAL
"""


@pytest.fixture
def tmpconfig(configfactory: ConfigFactory) -> Config:
Expand Down Expand Up @@ -253,6 +262,67 @@ def test_sasl_plain_no_password(
), 'No password is a configuration error and the bot must quit.'


def test_sasl_external_no_password(
botfactory: BotFactory,
configfactory: ConfigFactory,
) -> None:
tmpconfig = configfactory("conf.ini", TMP_CONFIG_SASL_EXTERNAL)
mockbot = botfactory.preloaded(tmpconfig, preloads=["coretasks"])
mockbot.backend.connected = True

# connect and capability negotiation
mockbot.on_connect()
mockbot.on_message(":irc.example.com CAP * LS :sasl=PLAIN,EXTERNAL")
n = len(mockbot.backend.message_sent)
mockbot.on_message(":irc.example.com CAP * ACK :sasl")
assert mockbot.backend.message_sent[n:] == rawlist(
"AUTHENTICATE EXTERNAL",
), "The bot should initiate SASL EXTERNAL authentication without a password set."
n += 1
mockbot.on_message("AUTHENTICATE +")
assert mockbot.backend.message_sent[n:] == rawlist(
"AUTHENTICATE +",
), "SASL EXTERNAL authentication should continue without a password set."
n += 1

mockbot.on_message(":irc.example.com 903 SopelTest :SASL authentication successful")
assert mockbot.backend.message_sent[n:] == rawlist(
"CAP END",
), "SASL success must resume capability negotiation."


def test_sasl_external_fail(
botfactory: BotFactory,
configfactory: ConfigFactory,
) -> None:
tmpconfig = configfactory("conf.ini", TMP_CONFIG_SASL_EXTERNAL)
mockbot = botfactory.preloaded(tmpconfig, preloads=["coretasks"])
mockbot.backend.connected = True

# connect and capability negotiation
mockbot.on_connect()
mockbot.on_message(":irc.example.com CAP * LS :sasl=PLAIN,EXTERNAL")
n = len(mockbot.backend.message_sent)

mockbot.on_message(":irc.example.com CAP * ACK :sasl")
assert mockbot.backend.message_sent[n:] == rawlist(
"AUTHENTICATE EXTERNAL",
), "The bot should initiate SASL EXTERNAL authentication without a password set."
n += 1

mockbot.on_message("AUTHENTICATE +")
assert mockbot.backend.message_sent[n:] == rawlist(
"AUTHENTICATE +",
), "SASL EXTERNAL authentication should continue without a password set."
n += 1

mockbot.on_message(":irc.example.com 904 SopelTest :SASL authentication failed")
assert mockbot.backend.message_sent[n:] == rawlist(
"CAP END",
"QUIT :SASL Auth Failed",
), "SASL EXTERNAL failure should trigger CAP END and QUIT."


def test_sasl_plain_bad_password(botfactory: BotFactory, tmpconfig) -> None:
mockbot = botfactory.preloaded(tmpconfig, preloads=['coretasks'])
mockbot.backend.connected = True
Expand Down

0 comments on commit 7b03d9c

Please sign in to comment.