Skip to content

Commit ae7ccba

Browse files
committed
fix(aws_lambda): handle null headers, query string, and body in API Gateway events
API Gateway sends null, not a missing key, for request fields that have no value. For example "headers", "multiValueHeaders" and "queryStringParameters" are null when the request has none of them. The Lambda adapter assumed these were always dicts and raised AttributeError before the request was dispatched. It also raised KeyError when "isBase64Encoded" was absent, and TypeError when the flag was true but the body was null. - Treat None the same as a missing key for every event field - Copy the headers dict so the caller's event is not modified - Add tests for null fields, a missing base64 flag, and a real base64-encoded body
1 parent a9dee3b commit ae7ccba

2 files changed

Lines changed: 69 additions & 9 deletions

File tree

‎slack_bolt/adapter/aws_lambda/handler.py‎

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -71,22 +71,26 @@ def handle(self, event, context):
7171

7272

7373
def to_bolt_request(event) -> BoltRequest:
74-
body = event.get("body", "")
75-
if event["isBase64Encoded"]:
74+
# API Gateway sends null (not a missing key) for fields that have no value,
75+
# such as "headers", "multiValueHeaders" and "queryStringParameters".
76+
# Every lookup below treats None the same as a missing key.
77+
body = event.get("body") or ""
78+
if event.get("isBase64Encoded") is True and body:
7679
body = base64.b64decode(body).decode("utf-8")
77-
cookies: Sequence[str] = event.get("cookies", [])
78-
if cookies is None or len(cookies) == 0:
80+
cookies: Sequence[str] = event.get("cookies") or []
81+
if len(cookies) == 0:
7982
# In the case of format v1
80-
multiValueHeaders = event.get("multiValueHeaders", {})
81-
cookies = multiValueHeaders.get("cookie", [])
83+
multiValueHeaders = event.get("multiValueHeaders") or {}
84+
cookies = multiValueHeaders.get("cookie") or []
8285
if len(cookies) == 0:
8386
# Try using uppercase
84-
cookies = multiValueHeaders.get("Cookie", [])
85-
headers = event.get("headers", {})
87+
cookies = multiValueHeaders.get("Cookie") or []
88+
# Copy the headers so the caller's event dict is left untouched
89+
headers = dict(event.get("headers") or {})
8690
headers["cookie"] = cookies
8791
return BoltRequest(
8892
body=body,
89-
query=event.get("queryStringParameters", {}),
93+
query=event.get("queryStringParameters") or {},
9094
headers=headers,
9195
)
9296

‎tests/adapter_tests/aws/test_aws_lambda.py‎

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import base64
12
import json
23
from time import time
34
from urllib.parse import quote
@@ -70,6 +71,61 @@ def test_not_found(self):
7071
response = not_found()
7172
assert response["statusCode"] == 404
7273

74+
def test_null_fields_in_api_gateway_event(self):
75+
# API Gateway sends null for headers, multiValueHeaders and queryStringParameters
76+
# when the request has none. These used to crash before the request was dispatched.
77+
app = App(client=self.web_client, signing_secret=self.signing_secret)
78+
event = {
79+
"httpMethod": "POST",
80+
"requestContext": {"httpMethod": "POST"},
81+
"headers": None,
82+
"multiValueHeaders": None,
83+
"queryStringParameters": None,
84+
"body": "{}",
85+
"isBase64Encoded": False,
86+
}
87+
response = SlackRequestHandler(app).handle(event, self.context)
88+
# No signature headers, so the request is rejected rather than crashing
89+
assert response["statusCode"] == 401
90+
# The caller's event must not be modified
91+
assert event["headers"] is None
92+
93+
def test_missing_is_base64_encoded_and_null_body(self):
94+
app = App(client=self.web_client, signing_secret=self.signing_secret)
95+
# isBase64Encoded is absent (for example when invoked from a test tool)
96+
event = {"requestContext": {"http": {"method": "POST"}}, "headers": {}, "body": "{}"}
97+
assert SlackRequestHandler(app).handle(event, self.context)["statusCode"] == 401
98+
# isBase64Encoded is true but the body is null
99+
event = {"requestContext": {"http": {"method": "POST"}}, "headers": {}, "body": None, "isBase64Encoded": True}
100+
assert SlackRequestHandler(app).handle(event, self.context)["statusCode"] == 401
101+
102+
def test_base64_encoded_body(self):
103+
app = App(client=self.web_client, signing_secret=self.signing_secret)
104+
105+
def event_handler():
106+
pass
107+
108+
app.event("app_mention")(event_handler)
109+
input = {
110+
"token": "verification_token",
111+
"team_id": "T111",
112+
"api_app_id": "A111",
113+
"event": {"type": "app_mention", "text": "<@W111> Hi", "user": "W222", "team": "T111", "channel": "C111"},
114+
"type": "event_callback",
115+
"event_id": "Ev111",
116+
"event_time": 1595926230,
117+
}
118+
timestamp, body = str(int(time())), json.dumps(input)
119+
event = {
120+
"body": base64.b64encode(body.encode("utf-8")).decode("ascii"),
121+
"queryStringParameters": None,
122+
"headers": self.build_headers(timestamp, body),
123+
"requestContext": {"http": {"method": "POST"}},
124+
"isBase64Encoded": True,
125+
}
126+
response = SlackRequestHandler(app).handle(event, self.context)
127+
assert response["statusCode"] == 200
128+
73129
def test_first_value(self):
74130
assert _first_value({"foo": [1, 2, 3]}, "foo") == 1
75131
assert _first_value({"foo": []}, "foo") is None

0 commit comments

Comments
 (0)