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
6 changes: 5 additions & 1 deletion slack_bolt/context/async_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,5 +116,9 @@ async def handle_button_clicks(ack, respond):
Callable `respond()` function
"""
if "respond" not in self:
self["respond"] = AsyncRespond(response_url=self.response_url)
self["respond"] = AsyncRespond(
response_url=self.response_url,
proxy=self.client.proxy,
ssl=self.client.ssl,
)
return self["respond"]
6 changes: 5 additions & 1 deletion slack_bolt/context/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,5 +118,9 @@ def handle_button_clicks(ack, respond):
Callable `respond()` function
"""
if "respond" not in self:
self["respond"] = Respond(response_url=self.response_url)
self["respond"] = Respond(
response_url=self.response_url,
proxy=self.client.proxy,
ssl=self.client.ssl,
)
return self["respond"]
21 changes: 18 additions & 3 deletions slack_bolt/context/respond/async_respond.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import Optional, Union, Sequence
from ssl import SSLContext

from slack_sdk.models.attachments import Attachment
from slack_sdk.models.blocks import Block
Expand All @@ -9,9 +10,19 @@

class AsyncRespond:
response_url: Optional[str]
proxy: Optional[str]
ssl: Optional[SSLContext]

def __init__(self, *, response_url: Optional[str]):
self.response_url: Optional[str] = response_url
def __init__(
self,
*,
response_url: Optional[str],
proxy: Optional[str] = None,
ssl: Optional[SSLContext] = None,
):
self.response_url = response_url
self.proxy = proxy
self.ssl = ssl

async def __call__(
self,
Expand All @@ -25,7 +36,11 @@ async def __call__(
unfurl_media: Optional[bool] = None,
) -> WebhookResponse:
if self.response_url is not None:
client = AsyncWebhookClient(self.response_url)
client = AsyncWebhookClient(
url=self.response_url,
proxy=self.proxy,
ssl=self.ssl,
)
text_or_whole_response: Union[str, dict] = text
if isinstance(text_or_whole_response, str):
message = _build_message(
Expand Down
21 changes: 18 additions & 3 deletions slack_bolt/context/respond/respond.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import Optional, Union, Sequence
from ssl import SSLContext

from slack_sdk.models.attachments import Attachment
from slack_sdk.models.blocks import Block
Expand All @@ -9,9 +10,19 @@

class Respond:
response_url: Optional[str]
proxy: Optional[str]
ssl: Optional[SSLContext]

def __init__(self, *, response_url: Optional[str]):
self.response_url: Optional[str] = response_url
def __init__(
self,
*,
response_url: Optional[str],
proxy: Optional[str] = None,
ssl: Optional[SSLContext] = None,
):
self.response_url = response_url
self.proxy = proxy
self.ssl = ssl

def __call__(
self,
Expand All @@ -25,7 +36,11 @@ def __call__(
unfurl_media: Optional[bool] = None,
) -> WebhookResponse:
if self.response_url is not None:
client = WebhookClient(self.response_url)
client = WebhookClient(
url=self.response_url,
proxy=self.proxy,
ssl=self.ssl,
)
text_or_whole_response: Union[str, dict] = text
if isinstance(text_or_whole_response, str):
text = text_or_whole_response
Expand Down
55 changes: 54 additions & 1 deletion tests/scenario_tests/test_app.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from concurrent.futures import Executor
from ssl import SSLContext

import pytest
from slack_sdk import WebClient
from slack_sdk.oauth.installation_store import FileInstallationStore
from slack_sdk.oauth.state_store import FileOAuthStateStore

from slack_bolt import App, Say, BoltRequest
from slack_bolt import App, Say, BoltRequest, BoltContext
from slack_bolt.authorization import AuthorizeResult
from slack_bolt.error import BoltError
from slack_bolt.oauth import OAuthFlow
Expand Down Expand Up @@ -240,3 +241,55 @@ def test_none_body_no_middleware(self):
response = app.dispatch(req)
assert response.status == 404
assert response.body == '{"error": "unhandled request"}'

def test_proxy_ssl_for_respond(self):
ssl = SSLContext()
web_client = WebClient(
token=self.valid_token,
base_url=self.mock_api_server_base_url,
proxy="http://proxy-host:9000/",
ssl=ssl,
)
app = App(
signing_secret="valid",
client=web_client,
authorize=lambda: AuthorizeResult(
enterprise_id="E111",
team_id="T111",
),
)

event_body = {
"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,
}

result = {"called": False}

@app.event("app_mention")
def handle(context: BoltContext, respond):
assert context.respond.proxy == "http://proxy-host:9000/"
assert context.respond.ssl == ssl
assert respond.proxy == "http://proxy-host:9000/"
assert respond.ssl == ssl
result["called"] = True

req = BoltRequest(body=event_body, headers={}, mode="socket_mode")
response = app.dispatch(req)
assert response.status == 200
assert result["called"] is True
84 changes: 84 additions & 0 deletions tests/scenario_tests_async/test_app.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,43 @@
import asyncio
from ssl import SSLContext

import pytest
from slack_sdk import WebClient
from slack_sdk.oauth.installation_store import FileInstallationStore
from slack_sdk.oauth.state_store import FileOAuthStateStore
from slack_sdk.web.async_client import AsyncWebClient

from slack_bolt.async_app import AsyncApp
from slack_bolt.authorization import AuthorizeResult
from slack_bolt.context.async_context import AsyncBoltContext
from slack_bolt.error import BoltError
from slack_bolt.oauth.async_oauth_flow import AsyncOAuthFlow
from slack_bolt.oauth.async_oauth_settings import AsyncOAuthSettings
from slack_bolt.request.async_request import AsyncBoltRequest
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 TestAsyncApp:
signing_secret = "secret"
valid_token = "xoxb-valid"
mock_api_server_base_url = "http://localhost:8888"

@pytest.fixture
def event_loop(self):
old_os_env = remove_os_env_temporarily()
try:
setup_mock_web_api_server(self)
loop = asyncio.get_event_loop()
yield loop
loop.close()
cleanup_mock_web_api_server(self)
finally:
restore_os_env(old_os_env)

def setup_method(self):
self.old_os_env = remove_os_env_temporarily()

Expand Down Expand Up @@ -163,3 +189,61 @@ def test_installation_store_conflicts(self):
installation_store=store1,
)
assert app.installation_store is store1

@pytest.mark.asyncio
async def test_proxy_ssl_for_respond(self):
ssl = SSLContext()
web_client = AsyncWebClient(
token=self.valid_token,
base_url=self.mock_api_server_base_url,
proxy="http://proxy-host:9000/",
ssl=ssl,
)

async def my_authorize():
return AuthorizeResult(
enterprise_id="E111",
team_id="T111",
)

app = AsyncApp(
signing_secret="valid",
client=web_client,
authorize=my_authorize,
)

event_body = {
"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,
}

result = {"called": False}

@app.event("app_mention")
async def handle(context: AsyncBoltContext, respond):
assert context.respond.proxy == "http://proxy-host:9000/"
assert context.respond.ssl == ssl
assert respond.proxy == "http://proxy-host:9000/"
assert respond.ssl == ssl
result["called"] = True

req = AsyncBoltRequest(body=event_body, headers={}, mode="socket_mode")
response = await app.async_dispatch(req)
assert response.status == 200
await asyncio.sleep(0.5) # wait a bit after auto ack()
assert result["called"] is True