From 07def73116dc700746f7050aa3699cd7867a85f3 Mon Sep 17 00:00:00 2001 From: Radoslav Georgiev Date: Sat, 24 Jul 2021 16:55:45 +0300 Subject: [PATCH 1/5] Add `solution` folder with initial change to `main.py` --- solution/main.py | 23 +++++++++++++++++++++++ solution/requirements.txt | 3 +++ 2 files changed, 26 insertions(+) create mode 100644 solution/main.py create mode 100644 solution/requirements.txt diff --git a/solution/main.py b/solution/main.py new file mode 100644 index 0000000..266841a --- /dev/null +++ b/solution/main.py @@ -0,0 +1,23 @@ +from pprint import pprint + +from fastapi import Body, FastAPI, Request, Response + +app = FastAPI() + + +@app.post("/echo") +async def echo(request: Request, response: Response, data=Body(...)): + raw_body = await request.body() + body = raw_body.decode("utf-8") + + # We need to add the `channels:read` scope + # From the `OAuth & Permissions` section in the bot settings + # This will give us `data.event.channel`, which is the channe id, + # So we can post back a message + pprint(data) + + return { + "data": data, + "raw_body": body, + "headers": request.headers + } diff --git a/solution/requirements.txt b/solution/requirements.txt new file mode 100644 index 0000000..eb7bf60 --- /dev/null +++ b/solution/requirements.txt @@ -0,0 +1,3 @@ +fastapi==0.67.0 +uvicorn==0.14.0 +requests==2.26.0 From 2ec1c38891dcee8fc366de4470c9e1dee63f237a Mon Sep 17 00:00:00 2001 From: Radoslav Georgiev Date: Sat, 24 Jul 2021 17:08:51 +0300 Subject: [PATCH 2/5] Handle `app_mention` and prepare to send a message back --- solution/main.py | 11 +++++++++++ solution/requirements.txt | 1 + 2 files changed, 12 insertions(+) diff --git a/solution/main.py b/solution/main.py index 266841a..0112fcb 100644 --- a/solution/main.py +++ b/solution/main.py @@ -1,5 +1,7 @@ from pprint import pprint +from benedict import benedict + from fastapi import Body, FastAPI, Request, Response app = FastAPI() @@ -16,6 +18,15 @@ async def echo(request: Request, response: Response, data=Body(...)): # So we can post back a message pprint(data) + payload = benedict(data) + + event_type = payload.get("event.type") + channel_id = payload.get("event.channel") + + if event_type == "app_mention": + # Send the message here + print(channel_id) + return { "data": data, "raw_body": body, diff --git a/solution/requirements.txt b/solution/requirements.txt index eb7bf60..3cc8d79 100644 --- a/solution/requirements.txt +++ b/solution/requirements.txt @@ -1,3 +1,4 @@ fastapi==0.67.0 uvicorn==0.14.0 requests==2.26.0 +python-benedict==0.24.0 From 073068036136b5e573b95de1ce8edb3b82e111fc Mon Sep 17 00:00:00 2001 From: Radoslav Georgiev Date: Sat, 24 Jul 2021 17:09:38 +0300 Subject: [PATCH 3/5] Send a message back to Slack, check the error that we got --- solution/main.py | 29 +++++++++++++++++++++++++++-- solution/requirements.txt | 1 + 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/solution/main.py b/solution/main.py index 0112fcb..58a5f9a 100644 --- a/solution/main.py +++ b/solution/main.py @@ -1,12 +1,23 @@ from pprint import pprint +import requests + from benedict import benedict +from dotenv import dotenv_values + from fastapi import Body, FastAPI, Request, Response +config = dotenv_values(".env") + app = FastAPI() +# This token can be obtained from the `OAuth Tokens for Your Workspace` section +# In `OAuth & Permissions` in the bot settings +BOT_TOKEN = config["BOT_TOKEN"] + + @app.post("/echo") async def echo(request: Request, response: Response, data=Body(...)): raw_body = await request.body() @@ -24,8 +35,22 @@ async def echo(request: Request, response: Response, data=Body(...)): channel_id = payload.get("event.channel") if event_type == "app_mention": - # Send the message here - print(channel_id) + send_message_payload = { + "channel": channel_id, + "text": "Pinging back in the channel" + } + + headers = { + "Authorization": f"Bearer {BOT_TOKEN}" + } + + response = requests.post( + "https://slack.com/api/chat.postMessage", + headers=headers, + json=send_message_payload + ) + # We do that to debug any incoming errors from Slack + pprint(response.text) return { "data": data, diff --git a/solution/requirements.txt b/solution/requirements.txt index 3cc8d79..8b948e1 100644 --- a/solution/requirements.txt +++ b/solution/requirements.txt @@ -2,3 +2,4 @@ fastapi==0.67.0 uvicorn==0.14.0 requests==2.26.0 python-benedict==0.24.0 +python-dotenv==0.18.0 From 30597c76715f8e30bdeed6538f965f90b4eb92f4 Mon Sep 17 00:00:00 2001 From: Radoslav Georgiev Date: Sat, 24 Jul 2021 17:18:08 +0300 Subject: [PATCH 4/5] Checking for `thread_ts` and replying back in a thread --- solution/main.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/solution/main.py b/solution/main.py index 58a5f9a..cf9dd7b 100644 --- a/solution/main.py +++ b/solution/main.py @@ -33,6 +33,7 @@ async def echo(request: Request, response: Response, data=Body(...)): event_type = payload.get("event.type") channel_id = payload.get("event.channel") + thread_ts = payload.get("event.thread_ts") if event_type == "app_mention": send_message_payload = { @@ -40,6 +41,10 @@ async def echo(request: Request, response: Response, data=Body(...)): "text": "Pinging back in the channel" } + if thread_ts is not None: + send_message_payload["thread_ts"] = thread_ts + send_message_payload["text"] = "Replying back in a thread" + headers = { "Authorization": f"Bearer {BOT_TOKEN}" } From f7d622d3dbb37ea55a04b3ffc73f8783fbbb8543 Mon Sep 17 00:00:00 2001 From: Radoslav Georgiev Date: Sat, 24 Jul 2021 17:22:57 +0300 Subject: [PATCH 5/5] Add Slack request verification --- solution/main.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/solution/main.py b/solution/main.py index cf9dd7b..359f079 100644 --- a/solution/main.py +++ b/solution/main.py @@ -1,3 +1,6 @@ +import hmac +import hashlib + from pprint import pprint import requests @@ -6,7 +9,7 @@ from dotenv import dotenv_values -from fastapi import Body, FastAPI, Request, Response +from fastapi import Body, FastAPI, Request, Response, status config = dotenv_values(".env") @@ -16,6 +19,19 @@ # This token can be obtained from the `OAuth Tokens for Your Workspace` section # In `OAuth & Permissions` in the bot settings BOT_TOKEN = config["BOT_TOKEN"] +SIGNING_SECRET = config["SIGNING_SECRET"] + + +def slack_validate_request(timestamp, body, slack_signature): + sig_basestring = 'v0:' + timestamp + ':' + body + + signature = 'v0=' + hmac.new( + SIGNING_SECRET.encode(), + sig_basestring.encode(), + hashlib.sha256 + ).hexdigest() + + return signature == slack_signature @app.post("/echo") @@ -23,6 +39,17 @@ async def echo(request: Request, response: Response, data=Body(...)): raw_body = await request.body() body = raw_body.decode("utf-8") + # We are following the guide from here: + # https://api.slack.com/authentication/verifying-requests-from-slack + slack_timestamp = request.headers['x-slack-request-timestamp'] + slack_signature = request.headers['x-slack-signature'] + + is_valid = slack_validate_request(slack_timestamp, body, slack_signature) + + if not is_valid: + response.status_code = status.HTTP_403_FORBIDDEN + return + # We need to add the `channels:read` scope # From the `OAuth & Permissions` section in the bot settings # This will give us `data.event.channel`, which is the channe id,