-
Notifications
You must be signed in to change notification settings - Fork 248
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
- Loading branch information
Showing
8 changed files
with
340 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import logging | ||
|
||
logging.basicConfig(level=logging.DEBUG) | ||
|
||
from slack_bolt.async_app import AsyncApp | ||
from slack_bolt.adapter.tornado.async_handler import AsyncSlackEventsHandler | ||
|
||
app = AsyncApp() | ||
|
||
|
||
@app.middleware # or app.use(log_request) | ||
async def log_request(logger, body, next_): | ||
logger.debug(body) | ||
await next_() | ||
|
||
|
||
@app.event("app_mention") | ||
async def event_test(body, say, logger): | ||
logger.info(body) | ||
await say("What's up?") | ||
|
||
|
||
from tornado.web import Application | ||
from tornado.ioloop import IOLoop | ||
|
||
api = Application([("/slack/events", AsyncSlackEventsHandler, dict(app=app))]) | ||
|
||
if __name__ == "__main__": | ||
api.listen(3000) | ||
IOLoop.current().start() | ||
|
||
# pip install -r requirements.txt | ||
# export SLACK_SIGNING_SECRET=*** | ||
# export SLACK_BOT_TOKEN=xoxb-*** | ||
# python async_app.py |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import logging | ||
from slack_bolt.async_app import AsyncApp | ||
from slack_bolt.adapter.tornado.async_handler import AsyncSlackEventsHandler, AsyncSlackOAuthHandler | ||
|
||
logging.basicConfig(level=logging.DEBUG) | ||
app = AsyncApp() | ||
|
||
|
||
@app.middleware # or app.use(log_request) | ||
async def log_request(logger, body, next_): | ||
logger.debug(body) | ||
await next_() | ||
|
||
|
||
@app.event("app_mention") | ||
async def event_test(body, say, logger): | ||
logger.info(body) | ||
await say("What's up?") | ||
|
||
|
||
from tornado.web import Application | ||
from tornado.ioloop import IOLoop | ||
|
||
api = Application( | ||
[ | ||
("/slack/events", AsyncSlackEventsHandler, dict(app=app)), | ||
("/slack/install", AsyncSlackOAuthHandler, dict(app=app)), | ||
("/slack/oauth_redirect", AsyncSlackOAuthHandler, dict(app=app)), | ||
] | ||
) | ||
|
||
if __name__ == "__main__": | ||
api.listen(3000) | ||
IOLoop.current().start() | ||
|
||
# pip install -r requirements.txt | ||
|
||
# # -- OAuth flow -- # | ||
# export SLACK_SIGNING_SECRET=*** | ||
# export SLACK_BOT_TOKEN=xoxb-*** | ||
# export SLACK_CLIENT_ID=111.111 | ||
# export SLACK_CLIENT_SECRET=*** | ||
# export SLACK_SCOPES=app_mentions:read,chat:write | ||
|
||
# python async_oauth_app.py |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
# Don't add async module imports here | ||
from .handler import SlackEventsHandler, SlackOAuthHandler | ||
|
||
__all__ = [ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
from tornado.httputil import HTTPServerRequest | ||
from tornado.web import RequestHandler | ||
|
||
from slack_bolt.async_app import AsyncApp | ||
from slack_bolt.oauth.async_oauth_flow import AsyncOAuthFlow | ||
from slack_bolt.request.async_request import AsyncBoltRequest | ||
from slack_bolt.response import BoltResponse | ||
from .handler import set_response | ||
|
||
|
||
class AsyncSlackEventsHandler(RequestHandler): | ||
def initialize(self, app: AsyncApp): # type: ignore | ||
self.app = app | ||
|
||
async def post(self): | ||
bolt_resp: BoltResponse = await self.app.async_dispatch(to_async_bolt_request(self.request)) | ||
set_response(self, bolt_resp) | ||
return | ||
|
||
|
||
class AsyncSlackOAuthHandler(RequestHandler): | ||
def initialize(self, app: AsyncApp): # type: ignore | ||
self.app = app | ||
|
||
async def get(self): | ||
if self.app.oauth_flow is not None: # type: ignore | ||
oauth_flow: AsyncOAuthFlow = self.app.oauth_flow # type: ignore | ||
if self.request.path == oauth_flow.install_path: | ||
bolt_resp = await oauth_flow.handle_installation(to_async_bolt_request(self.request)) | ||
set_response(self, bolt_resp) | ||
return | ||
elif self.request.path == oauth_flow.redirect_uri_path: | ||
bolt_resp = await oauth_flow.handle_callback(to_async_bolt_request(self.request)) | ||
set_response(self, bolt_resp) | ||
return | ||
self.set_status(404) | ||
|
||
|
||
def to_async_bolt_request(req: HTTPServerRequest) -> AsyncBoltRequest: | ||
return AsyncBoltRequest( | ||
body=req.body.decode("utf-8") if req.body else "", | ||
query=req.query, | ||
headers=req.headers, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
import json | ||
from time import time | ||
from urllib.parse import quote | ||
|
||
from slack_sdk.signature import SignatureVerifier | ||
from slack_sdk.web.async_client import AsyncWebClient | ||
from tornado.httpclient import HTTPRequest, HTTPResponse | ||
from tornado.testing import AsyncHTTPTestCase, gen_test | ||
from tornado.web import Application | ||
|
||
from slack_bolt.adapter.tornado.async_handler import AsyncSlackEventsHandler | ||
from slack_bolt.async_app import AsyncApp | ||
from tests.mock_web_api_server import ( | ||
setup_mock_web_api_server, | ||
cleanup_mock_web_api_server, | ||
assert_auth_test_count, | ||
) | ||
from tests.utils import remove_os_env_temporarily, restore_os_env | ||
|
||
signing_secret = "secret" | ||
valid_token = "xoxb-valid" | ||
mock_api_server_base_url = "http://localhost:8888" | ||
|
||
|
||
async def event_handler(): | ||
pass | ||
|
||
|
||
async def shortcut_handler(ack): | ||
await ack() | ||
|
||
|
||
async def command_handler(ack): | ||
await ack() | ||
|
||
|
||
class TestTornado(AsyncHTTPTestCase): | ||
signature_verifier = SignatureVerifier(signing_secret) | ||
|
||
def setUp(self): | ||
self.old_os_env = remove_os_env_temporarily() | ||
setup_mock_web_api_server(self) | ||
|
||
web_client = AsyncWebClient( | ||
token=valid_token, | ||
base_url=mock_api_server_base_url, | ||
) | ||
self.app = AsyncApp( | ||
client=web_client, | ||
signing_secret=signing_secret, | ||
) | ||
self.app.event("app_mention")(event_handler) | ||
self.app.shortcut("test-shortcut")(shortcut_handler) | ||
self.app.command("/hello-world")(command_handler) | ||
|
||
AsyncHTTPTestCase.setUp(self) | ||
|
||
def tearDown(self): | ||
AsyncHTTPTestCase.tearDown(self) | ||
cleanup_mock_web_api_server(self) | ||
restore_os_env(self.old_os_env) | ||
|
||
def get_app(self): | ||
return Application([("/slack/events", AsyncSlackEventsHandler, dict(app=self.app))]) | ||
|
||
def generate_signature(self, body: str, timestamp: str): | ||
return self.signature_verifier.generate_signature( | ||
body=body, | ||
timestamp=timestamp, | ||
) | ||
|
||
def build_headers(self, timestamp: str, body: str): | ||
content_type = "application/json" if body.startswith("{") else "application/x-www-form-urlencoded" | ||
return { | ||
"content-type": content_type, | ||
"x-slack-signature": self.generate_signature(body, timestamp), | ||
"x-slack-request-timestamp": timestamp, | ||
} | ||
|
||
@gen_test | ||
async def test_events(self): | ||
input = { | ||
"token": "verification_token", | ||
"team_id": "T111", | ||
"enterprise_id": "E111", | ||
"api_app_id": "A111", | ||
"event": { | ||
"client_msg_id": "9cbd4c5b-7ddf-4ede-b479-ad21fca66d63", | ||
"type": "app_mention", | ||
"text": "<@W111> Hi there!", | ||
"user": "W222", | ||
"ts": "1595926230.009600", | ||
"team": "T111", | ||
"channel": "C111", | ||
"event_ts": "1595926230.009600", | ||
}, | ||
"type": "event_callback", | ||
"event_id": "Ev111", | ||
"event_time": 1595926230, | ||
"authed_users": ["W111"], | ||
} | ||
timestamp, body = str(int(time())), json.dumps(input) | ||
|
||
request = HTTPRequest( | ||
url=self.get_url("/slack/events"), | ||
method="POST", | ||
body=body, | ||
headers=self.build_headers(timestamp, body), | ||
) | ||
response: HTTPResponse = await self.http_client.fetch(request) | ||
assert response.code == 200 | ||
assert_auth_test_count(self, 1) | ||
|
||
@gen_test | ||
async def test_shortcuts(self): | ||
input = { | ||
"type": "shortcut", | ||
"token": "verification_token", | ||
"action_ts": "111.111", | ||
"team": { | ||
"id": "T111", | ||
"domain": "workspace-domain", | ||
"enterprise_id": "E111", | ||
"enterprise_name": "Org Name", | ||
}, | ||
"user": {"id": "W111", "username": "primary-owner", "team_id": "T111"}, | ||
"callback_id": "test-shortcut", | ||
"trigger_id": "111.111.xxxxxx", | ||
} | ||
|
||
timestamp, body = str(int(time())), f"payload={quote(json.dumps(input))}" | ||
|
||
request = HTTPRequest( | ||
url=self.get_url("/slack/events"), | ||
method="POST", | ||
body=body, | ||
headers=self.build_headers(timestamp, body), | ||
) | ||
response: HTTPResponse = await self.http_client.fetch(request) | ||
assert response.code == 200 | ||
assert_auth_test_count(self, 1) | ||
|
||
@gen_test | ||
async def test_commands(self): | ||
input = ( | ||
"token=verification_token" | ||
"&team_id=T111" | ||
"&team_domain=test-domain" | ||
"&channel_id=C111" | ||
"&channel_name=random" | ||
"&user_id=W111" | ||
"&user_name=primary-owner" | ||
"&command=%2Fhello-world" | ||
"&text=Hi" | ||
"&enterprise_id=E111" | ||
"&enterprise_name=Org+Name" | ||
"&response_url=https%3A%2F%2Fhooks.slack.com%2Fcommands%2FT111%2F111%2Fxxxxx" | ||
"&trigger_id=111.111.xxx" | ||
) | ||
timestamp, body = str(int(time())), input | ||
|
||
request = HTTPRequest( | ||
url=self.get_url("/slack/events"), | ||
method="POST", | ||
body=body, | ||
headers=self.build_headers(timestamp, body), | ||
) | ||
response: HTTPResponse = await self.http_client.fetch(request) | ||
assert response.code == 200 | ||
assert_auth_test_count(self, 1) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
from tornado.httpclient import HTTPRequest, HTTPResponse, HTTPClientError | ||
from tornado.testing import AsyncHTTPTestCase, gen_test | ||
from tornado.web import Application | ||
|
||
from slack_bolt.adapter.tornado.async_handler import AsyncSlackOAuthHandler | ||
from slack_bolt.async_app import AsyncApp | ||
from slack_bolt.oauth.async_oauth_settings import AsyncOAuthSettings | ||
from tests.utils import remove_os_env_temporarily, restore_os_env | ||
|
||
signing_secret = "secret" | ||
|
||
app = AsyncApp( | ||
signing_secret=signing_secret, | ||
oauth_settings=AsyncOAuthSettings( | ||
client_id="111.111", | ||
client_secret="xxx", | ||
scopes=["chat:write", "commands"], | ||
), | ||
) | ||
|
||
|
||
class TestTornado(AsyncHTTPTestCase): | ||
def get_app(self): | ||
return Application([("/slack/install", AsyncSlackOAuthHandler, dict(app=app))]) | ||
|
||
def setUp(self): | ||
AsyncHTTPTestCase.setUp(self) | ||
self.old_os_env = remove_os_env_temporarily() | ||
|
||
def tearDown(self): | ||
AsyncHTTPTestCase.tearDown(self) | ||
restore_os_env(self.old_os_env) | ||
|
||
@gen_test | ||
async def test_oauth(self): | ||
request = HTTPRequest(url=self.get_url("/slack/install"), method="GET", follow_redirects=False) | ||
try: | ||
response: HTTPResponse = await self.http_client.fetch(request) | ||
assert response.code == 200 | ||
except HTTPClientError as e: | ||
assert e.code == 200 |