X Tutup
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion samples/bottle/oauth_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,4 @@ def oauth_redirect():
# export SLACK_CLIENT_SECRET=***
# export SLACK_SCOPES=app_mentions:read,chat:write

# FLASK_APP=oauth_app.py FLASK_ENV=development flask run -p 3000
# python oauth_app.py
54 changes: 54 additions & 0 deletions samples/cherrypy/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# ------------------------------------------------
# instead of slack_bolt in requirements.txt
import sys

sys.path.insert(1, "../..")
# ------------------------------------------------

import logging

logging.basicConfig(level=logging.DEBUG)

from slack_bolt import App
from slack_bolt.adapter.cherrypy import SlackRequestHandler

app = App()


@app.middleware # or app.use(log_request)
def log_request(logger, payload, next):
logger.debug(payload)
return next()


@app.command("/hello-bolt-python")
def hello_command(ack):
ack("Hi from CherryPy")


@app.event("app_mention")
def event_test(payload, say, logger):
logger.info(payload)
say("What's up?")


import cherrypy

handler = SlackRequestHandler(app)


class SlackApp(object):
@cherrypy.expose
@cherrypy.tools.slack_in()
def events(self, **kwargs):
return handler.handle()


if __name__ == "__main__":
cherrypy.config.update({"server.socket_port": 3000})
cherrypy.quickstart(SlackApp(), "/slack")

# pip install -r requirements.txt
# export SLACK_SIGNING_SECRET=***
# export SLACK_BOT_TOKEN=xoxb-***
# python app.py
68 changes: 68 additions & 0 deletions samples/cherrypy/oauth_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# ------------------------------------------------
# instead of slack_bolt in requirements.txt
import sys

sys.path.insert(1, "../../src")
# ------------------------------------------------

import logging
from slack_bolt import App
from slack_bolt.adapter.cherrypy import SlackRequestHandler

logging.basicConfig(level=logging.DEBUG)
app = App()


@app.middleware # or app.use(log_request)
def log_request(logger, payload, next):
logger.debug(payload)
return next()


@app.command("/hello-bolt-python")
def hello_command(ack):
ack("Hi from CherryPy")


@app.event("app_mention")
def event_test(payload, say, logger):
logger.info(payload)
say("What's up?")


import cherrypy

handler = SlackRequestHandler(app)


class SlackApp(object):
@cherrypy.expose
@cherrypy.tools.slack_in()
def events(self, **kwargs):
return handler.handle()

@cherrypy.expose
@cherrypy.tools.slack_in()
def install(self, **kwargs):
return handler.handle()

@cherrypy.expose
@cherrypy.tools.slack_in()
def oauth_redirect(self, **kwargs):
return handler.handle()


if __name__ == "__main__":
cherrypy.config.update({"server.socket_port": 3000})
cherrypy.quickstart(SlackApp(), "/slack")

# 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 oauth_app.py
1 change: 1 addition & 0 deletions samples/cherrypy/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CherryPy>=18,<19
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"bottle>=0.12,<1",
"chalice>=1,<2",
"click>=7,<8", # for chalice
"CherryPy>=18,<19",
"Django>=3,<4",
"falcon>=2,<3",
"fastapi<1",
Expand Down
1 change: 1 addition & 0 deletions slack_bolt/adapter/cherrypy/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .handler import SlackRequestHandler
77 changes: 77 additions & 0 deletions slack_bolt/adapter/cherrypy/handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
from typing import Optional

import cherrypy

from slack_bolt.app import App
from slack_bolt.oauth import OAuthFlow
from slack_bolt.request import BoltRequest
from slack_bolt.response import BoltResponse


def build_bolt_request() -> BoltRequest:
req = cherrypy.request
body = req.raw_body if hasattr(req, "raw_body") else ""
return BoltRequest(body=body, query=req.query_string, headers=req.headers,)


def set_response_status_and_headers(bolt_resp: BoltResponse) -> None:
cherrypy.response.status = bolt_resp.status
for k, v in bolt_resp.first_headers_without_set_cookie().items():
cherrypy.response.headers[k] = v
for cookie in bolt_resp.cookies():
for name, c in cookie.items():
str_max_age: Optional[str] = c.get("max-age", None)
max_age: Optional[int] = int(str_max_age) if str_max_age else None
cherrypy_cookie = cherrypy.response.cookie
cherrypy_cookie[name] = c.value
cherrypy_cookie[name]["expires"] = c.get("expires", None)
cherrypy_cookie[name]["max-age"] = max_age
cherrypy_cookie[name]["domain"] = c.get("domain", None)
cherrypy_cookie[name]["path"] = c.get("path", None)
cherrypy_cookie[name]["secure"] = True
cherrypy_cookie[name]["httponly"] = True


@cherrypy.tools.register("on_start_resource")
def slack_in():
request = cherrypy.serving.request

def slack_processor(entity):
try:
if request.process_request_body:
body = entity.fp.read()
body = body.decode("utf-8") if isinstance(body, bytes) else ""
request.raw_body = body
except ValueError:
raise cherrypy.HTTPError(400, "Invalid request body")

request.body.processors.clear()
request.body.processors["application/json"] = slack_processor
request.body.processors["application/x-www-form-urlencoded"] = slack_processor


class SlackRequestHandler:
def __init__(self, app: App): # type: ignore
self.app = app

def handle(self) -> bytes:
req = cherrypy.request
if req.method == "GET":
if self.app.oauth_flow is not None:
oauth_flow: OAuthFlow = self.app.oauth_flow
request_path = req.wsgi_environ["REQUEST_URI"].split("?")[0]
if request_path == oauth_flow.install_path:
bolt_resp = oauth_flow.handle_installation(build_bolt_request())
set_response_status_and_headers(bolt_resp)
return (bolt_resp.body or "").encode("utf-8")
elif request_path == oauth_flow.redirect_uri_path:
bolt_resp = oauth_flow.handle_callback(build_bolt_request())
set_response_status_and_headers(bolt_resp)
return (bolt_resp.body or "").encode("utf-8")
elif req.method == "POST":
bolt_resp: BoltResponse = self.app.dispatch(build_bolt_request())
set_response_status_and_headers(bolt_resp)
return (bolt_resp.body or "").encode("utf-8")

cherrypy.response.status = 404
return "Not Found".encode("utf-8")
159 changes: 159 additions & 0 deletions tests/adapter_tests/test_cherrypy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import json
from time import time

import cherrypy
from cherrypy.test import helper
from slack_sdk.signature import SignatureVerifier
from slack_sdk.web import WebClient

from slack_bolt.adapter.cherrypy import SlackRequestHandler
from slack_bolt.app import App
from tests.mock_web_api_server import (
setup_mock_web_api_server,
cleanup_mock_web_api_server,
)
from tests.utils import remove_os_env_temporarily, restore_os_env


class TestCherryPy(helper.CPWebCase):
helper.CPWebCase.interactive = False
signing_secret = "secret"
signature_verifier = SignatureVerifier(signing_secret)

@classmethod
def setup_server(cls):
cls.old_os_env = remove_os_env_temporarily()
setup_mock_web_api_server(cls)

signing_secret = "secret"
valid_token = "xoxb-valid"
mock_api_server_base_url = "http://localhost:8888"
web_client = WebClient(token=valid_token, base_url=mock_api_server_base_url,)
app = App(client=web_client, signing_secret=signing_secret,)

def event_handler():
pass

def shortcut_handler(ack):
ack()

def command_handler(ack):
ack()

app.event("app_mention")(event_handler)
app.shortcut("test-shortcut")(shortcut_handler)
app.command("/hello-world")(command_handler)

handler = SlackRequestHandler(app)

class SlackApp(object):
@cherrypy.expose
@cherrypy.tools.slack_in()
def events(self, **kwargs):
return handler.handle()

cherrypy.tree.mount(SlackApp(), "/slack")

@classmethod
def teardown_class(cls):
cls.supervisor.stop()
cleanup_mock_web_api_server(cls)
restore_os_env(cls.old_os_env)

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):
return [
("content-length", str(len(body))),
("x-slack-signature", self.generate_signature(body, timestamp)),
("x-slack-request-timestamp", timestamp),
]

def test_events(self):
payload = {
"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(payload)
cherrypy.request.process_request_body = True
self.getPage(
"/slack/events",
method="POST",
body=body,
headers=self.build_headers(timestamp, body),
)
self.assertStatus("200 OK")
self.assertBody("")

def test_shortcuts(self):
payload = {
"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())), json.dumps(payload)
cherrypy.request.process_request_body = True
self.getPage(
"/slack/events",
method="POST",
body=body,
headers=self.build_headers(timestamp, body),
)
self.assertStatus("200 OK")
self.assertBody("")

def test_commands(self):
payload = (
"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())), json.dumps(payload)
cherrypy.request.process_request_body = True
self.getPage(
"/slack/events",
method="POST",
body=body,
headers=self.build_headers(timestamp, body),
)
self.assertStatus("200 OK")
self.assertBody("")
Loading
X Tutup