Repository navigation
fix(aws_lambda): handle null headers, query string, and body in API Gateway events #1587
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -71,22 +71,26 @@ def handle(self, event, context): | |||
|
|
||||
|
|
||||
| def to_bolt_request(event) -> BoltRequest: | ||||
| body = event.get("body", "") | ||||
| if event["isBase64Encoded"]: | ||||
| # API Gateway sends null (not a missing key) for fields that have no value, | ||||
| # such as "headers", "multiValueHeaders" and "queryStringParameters". | ||||
| # Every lookup below treats None the same as a missing key. | ||||
| body = event.get("body") or "" | ||||
| if event.get("isBase64Encoded") is True and body: | ||||
| body = base64.b64decode(body).decode("utf-8") | ||||
| cookies: Sequence[str] = event.get("cookies", []) | ||||
| if cookies is None or len(cookies) == 0: | ||||
| cookies: Sequence[str] = event.get("cookies") or [] | ||||
| if len(cookies) == 0: | ||||
| # In the case of format v1 | ||||
| multiValueHeaders = event.get("multiValueHeaders", {}) | ||||
| cookies = multiValueHeaders.get("cookie", []) | ||||
| multiValueHeaders = event.get("multiValueHeaders") or {} | ||||
| cookies = multiValueHeaders.get("cookie") or [] | ||||
| if len(cookies) == 0: | ||||
| # Try using uppercase | ||||
| cookies = multiValueHeaders.get("Cookie", []) | ||||
| headers = event.get("headers", {}) | ||||
| cookies = multiValueHeaders.get("Cookie") or [] | ||||
| # Copy the headers so the caller's event dict is left untouched | ||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same as above
Suggested change
|
||||
| headers = dict(event.get("headers") or {}) | ||||
| headers["cookie"] = cookies | ||||
| return BoltRequest( | ||||
| body=body, | ||||
| query=event.get("queryStringParameters", {}), | ||||
| query=event.get("queryStringParameters") or {}, | ||||
| headers=headers, | ||||
| ) | ||||
|
|
||||
|
|
||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,3 +1,4 @@ | ||||||
| import base64 | ||||||
| import json | ||||||
| from time import time | ||||||
| from urllib.parse import quote | ||||||
|
|
@@ -70,6 +71,61 @@ def test_not_found(self): | |||||
| response = not_found() | ||||||
| assert response["statusCode"] == 404 | ||||||
|
|
||||||
| def test_null_fields_in_api_gateway_event(self): | ||||||
| # API Gateway sends null for headers, multiValueHeaders and queryStringParameters | ||||||
| # when the request has none. These used to crash before the request was dispatched. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same as above
Suggested change
|
||||||
| app = App(client=self.web_client, signing_secret=self.signing_secret) | ||||||
| event = { | ||||||
| "httpMethod": "POST", | ||||||
| "requestContext": {"httpMethod": "POST"}, | ||||||
| "headers": None, | ||||||
| "multiValueHeaders": None, | ||||||
| "queryStringParameters": None, | ||||||
| "body": "{}", | ||||||
| "isBase64Encoded": False, | ||||||
| } | ||||||
| response = SlackRequestHandler(app).handle(event, self.context) | ||||||
| # No signature headers, so the request is rejected rather than crashing | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| assert response["statusCode"] == 401 | ||||||
| # The caller's event must not be modified | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| assert event["headers"] is None | ||||||
|
|
||||||
| def test_missing_is_base64_encoded_and_null_body(self): | ||||||
| app = App(client=self.web_client, signing_secret=self.signing_secret) | ||||||
| # isBase64Encoded is absent (for example when invoked from a test tool) | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| event = {"requestContext": {"http": {"method": "POST"}}, "headers": {}, "body": "{}"} | ||||||
| assert SlackRequestHandler(app).handle(event, self.context)["statusCode"] == 401 | ||||||
| # isBase64Encoded is true but the body is null | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| event = {"requestContext": {"http": {"method": "POST"}}, "headers": {}, "body": None, "isBase64Encoded": True} | ||||||
| assert SlackRequestHandler(app).handle(event, self.context)["statusCode"] == 401 | ||||||
|
|
||||||
| def test_base64_encoded_body(self): | ||||||
| app = App(client=self.web_client, signing_secret=self.signing_secret) | ||||||
|
|
||||||
| def event_handler(): | ||||||
| pass | ||||||
|
|
||||||
| app.event("app_mention")(event_handler) | ||||||
| input = { | ||||||
| "token": "verification_token", | ||||||
| "team_id": "T111", | ||||||
| "api_app_id": "A111", | ||||||
| "event": {"type": "app_mention", "text": "<@W111> Hi", "user": "W222", "team": "T111", "channel": "C111"}, | ||||||
| "type": "event_callback", | ||||||
| "event_id": "Ev111", | ||||||
| "event_time": 1595926230, | ||||||
| } | ||||||
| timestamp, body = str(int(time())), json.dumps(input) | ||||||
| event = { | ||||||
| "body": base64.b64encode(body.encode("utf-8")).decode("ascii"), | ||||||
| "queryStringParameters": None, | ||||||
| "headers": self.build_headers(timestamp, body), | ||||||
| "requestContext": {"http": {"method": "POST"}}, | ||||||
| "isBase64Encoded": True, | ||||||
| } | ||||||
| response = SlackRequestHandler(app).handle(event, self.context) | ||||||
| assert response["statusCode"] == 200 | ||||||
|
|
||||||
| def test_first_value(self): | ||||||
| assert _first_value({"foo": [1, 2, 3]}, "foo") == 1 | ||||||
| assert _first_value({"foo": []}, "foo") is None | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lets get rid of these comments