mirror of
https://github.com/Sea-Haven-Industries/afterhours-shift-manager.git
synced 2026-09-30 07:53:11 +00:00
fix(slack-bot): return 400 on malformed request bodies (#254)
Bolt parse_body raises JSONDecodeError for empty or non-JSON form payload fields, which turned probe POSTs into unhandled Lambda 500s. Fixes AFTERHOURS-SHIFT-MANAGER-2
This commit is contained in:
parent
7a513b30ca
commit
9544dd696a
3 changed files with 111 additions and 1 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
"""Lambda handler — Slack Bolt app entry point."""
|
"""Lambda handler — Slack Bolt app entry point."""
|
||||||
|
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
@ -33,4 +34,8 @@ def _get_handler() -> SlackRequestHandler:
|
||||||
|
|
||||||
|
|
||||||
def handler(event, context):
|
def handler(event, context):
|
||||||
return _get_handler().handle(event, context)
|
try:
|
||||||
|
return _get_handler().handle(event, context)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
logger.warning("rejecting malformed slack request body")
|
||||||
|
return {"statusCode": 400, "body": "invalid request"}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,35 @@ def slackbot_app():
|
||||||
return _load("slackbot_app", "src/slack-bot/app.py")
|
return _load("slackbot_app", "src/slack-bot/app.py")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def slackbot_handler(monkeypatch):
|
||||||
|
"""Load handler.py; skip Slack auth.test so request parsing is the unit."""
|
||||||
|
from slack_bolt import App
|
||||||
|
from slack_bolt.adapter.aws_lambda import SlackRequestHandler
|
||||||
|
|
||||||
|
slack_bot_dir = str(_ROOT / "src/slack-bot")
|
||||||
|
monkeypatch.syspath_prepend(slack_bot_dir)
|
||||||
|
saved = {name: sys.modules.get(name) for name in ("app", "handler")}
|
||||||
|
for name in saved:
|
||||||
|
sys.modules.pop(name, None)
|
||||||
|
mod = _load("slackbot_handler", "src/slack-bot/handler.py")
|
||||||
|
bolt_app = App(
|
||||||
|
token="xoxb-test",
|
||||||
|
signing_secret="test-secret",
|
||||||
|
process_before_response=True,
|
||||||
|
token_verification_enabled=False,
|
||||||
|
)
|
||||||
|
wrapped = SlackRequestHandler(app=bolt_app)
|
||||||
|
monkeypatch.setattr(mod, "_get_handler", lambda: wrapped)
|
||||||
|
yield mod
|
||||||
|
mod._slack_handler = None
|
||||||
|
for name, previous in saved.items():
|
||||||
|
if previous is None:
|
||||||
|
sys.modules.pop(name, None)
|
||||||
|
else:
|
||||||
|
sys.modules[name] = previous
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def respond():
|
def respond():
|
||||||
return MagicMock(name="respond")
|
return MagicMock(name="respond")
|
||||||
|
|
|
||||||
76
tests/slack_bot/test_handler.py
Normal file
76
tests/slack_bot/test_handler.py
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
"""Slack-bot Lambda handler: malformed bodies return 400, not 500."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from slack_bolt.request.internals import parse_body
|
||||||
|
|
||||||
|
|
||||||
|
def _context():
|
||||||
|
return SimpleNamespace(
|
||||||
|
function_name="afterhours-shift-manager",
|
||||||
|
invoked_function_arn="arn:aws:lambda:us-east-1:1:function:x",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _event(body, content_type, user_agent="Python-urllib/3.12"):
|
||||||
|
return {
|
||||||
|
"version": "2.0",
|
||||||
|
"routeKey": "POST /slack/events",
|
||||||
|
"rawPath": "/slack/events",
|
||||||
|
"headers": {
|
||||||
|
"content-type": content_type,
|
||||||
|
"user-agent": user_agent,
|
||||||
|
},
|
||||||
|
"requestContext": {"http": {"method": "POST", "path": "/slack/events"}},
|
||||||
|
"body": body,
|
||||||
|
"isBase64Encoded": False,
|
||||||
|
"queryStringParameters": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_body_empty_payload_raises():
|
||||||
|
with pytest.raises(json.JSONDecodeError):
|
||||||
|
parse_body("payload=", "application/x-www-form-urlencoded")
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_body_non_json_payload_raises():
|
||||||
|
with pytest.raises(json.JSONDecodeError):
|
||||||
|
parse_body("payload=not-json", "application/x-www-form-urlencoded")
|
||||||
|
|
||||||
|
|
||||||
|
def test_empty_form_payload_returns_400(slackbot_handler):
|
||||||
|
result = slackbot_handler.handler(
|
||||||
|
_event("payload=", "application/x-www-form-urlencoded"), _context()
|
||||||
|
)
|
||||||
|
assert result["statusCode"] == 400
|
||||||
|
assert result["body"] == "invalid request"
|
||||||
|
|
||||||
|
|
||||||
|
def test_non_json_form_payload_returns_400(slackbot_handler):
|
||||||
|
result = slackbot_handler.handler(
|
||||||
|
_event("payload=not-json", "application/x-www-form-urlencoded"),
|
||||||
|
_context(),
|
||||||
|
)
|
||||||
|
assert result["statusCode"] == 400
|
||||||
|
assert result["body"] == "invalid request"
|
||||||
|
|
||||||
|
|
||||||
|
def test_payload_substring_without_form_key_is_unsigned(slackbot_handler):
|
||||||
|
result = slackbot_handler.handler(
|
||||||
|
_event("hello payload world", "text/plain"), _context()
|
||||||
|
)
|
||||||
|
assert result["statusCode"] in (401, 403)
|
||||||
|
|
||||||
|
|
||||||
|
def test_json_events_body_is_unsigned_not_jsondecode(slackbot_handler):
|
||||||
|
result = slackbot_handler.handler(
|
||||||
|
_event(
|
||||||
|
'{"type":"url_verification","challenge":"abc"}',
|
||||||
|
"application/json",
|
||||||
|
user_agent="Slackbot 1.0",
|
||||||
|
),
|
||||||
|
_context(),
|
||||||
|
)
|
||||||
|
assert result["statusCode"] in (401, 403)
|
||||||
Loading…
Add table
Reference in a new issue