Skip to content

Commit 3508337

Browse files
authored
Fix #604 Respect the proxy_url in respond (#608)
1 parent a3858ea commit 3508337

File tree

6 files changed

+184
-9
lines changed

6 files changed

+184
-9
lines changed

slack_bolt/context/async_context.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,5 +116,9 @@ async def handle_button_clicks(ack, respond):
116116
Callable `respond()` function
117117
"""
118118
if "respond" not in self:
119-
self["respond"] = AsyncRespond(response_url=self.response_url)
119+
self["respond"] = AsyncRespond(
120+
response_url=self.response_url,
121+
proxy=self.client.proxy,
122+
ssl=self.client.ssl,
123+
)
120124
return self["respond"]

slack_bolt/context/context.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,5 +118,9 @@ def handle_button_clicks(ack, respond):
118118
Callable `respond()` function
119119
"""
120120
if "respond" not in self:
121-
self["respond"] = Respond(response_url=self.response_url)
121+
self["respond"] = Respond(
122+
response_url=self.response_url,
123+
proxy=self.client.proxy,
124+
ssl=self.client.ssl,
125+
)
122126
return self["respond"]

slack_bolt/context/respond/async_respond.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from typing import Optional, Union, Sequence
2+
from ssl import SSLContext
23

34
from slack_sdk.models.attachments import Attachment
45
from slack_sdk.models.blocks import Block
@@ -9,9 +10,19 @@
910

1011
class AsyncRespond:
1112
response_url: Optional[str]
13+
proxy: Optional[str]
14+
ssl: Optional[SSLContext]
1215

13-
def __init__(self, *, response_url: Optional[str]):
14-
self.response_url: Optional[str] = response_url
16+
def __init__(
17+
self,
18+
*,
19+
response_url: Optional[str],
20+
proxy: Optional[str] = None,
21+
ssl: Optional[SSLContext] = None,
22+
):
23+
self.response_url = response_url
24+
self.proxy = proxy
25+
self.ssl = ssl
1526

1627
async def __call__(
1728
self,
@@ -25,7 +36,11 @@ async def __call__(
2536
unfurl_media: Optional[bool] = None,
2637
) -> WebhookResponse:
2738
if self.response_url is not None:
28-
client = AsyncWebhookClient(self.response_url)
39+
client = AsyncWebhookClient(
40+
url=self.response_url,
41+
proxy=self.proxy,
42+
ssl=self.ssl,
43+
)
2944
text_or_whole_response: Union[str, dict] = text
3045
if isinstance(text_or_whole_response, str):
3146
message = _build_message(

slack_bolt/context/respond/respond.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from typing import Optional, Union, Sequence
2+
from ssl import SSLContext
23

34
from slack_sdk.models.attachments import Attachment
45
from slack_sdk.models.blocks import Block
@@ -9,9 +10,19 @@
910

1011
class Respond:
1112
response_url: Optional[str]
13+
proxy: Optional[str]
14+
ssl: Optional[SSLContext]
1215

13-
def __init__(self, *, response_url: Optional[str]):
14-
self.response_url: Optional[str] = response_url
16+
def __init__(
17+
self,
18+
*,
19+
response_url: Optional[str],
20+
proxy: Optional[str] = None,
21+
ssl: Optional[SSLContext] = None,
22+
):
23+
self.response_url = response_url
24+
self.proxy = proxy
25+
self.ssl = ssl
1526

1627
def __call__(
1728
self,
@@ -25,7 +36,11 @@ def __call__(
2536
unfurl_media: Optional[bool] = None,
2637
) -> WebhookResponse:
2738
if self.response_url is not None:
28-
client = WebhookClient(self.response_url)
39+
client = WebhookClient(
40+
url=self.response_url,
41+
proxy=self.proxy,
42+
ssl=self.ssl,
43+
)
2944
text_or_whole_response: Union[str, dict] = text
3045
if isinstance(text_or_whole_response, str):
3146
text = text_or_whole_response

tests/scenario_tests/test_app.py

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
from concurrent.futures import Executor
2+
from ssl import SSLContext
23

34
import pytest
45
from slack_sdk import WebClient
56
from slack_sdk.oauth.installation_store import FileInstallationStore
67
from slack_sdk.oauth.state_store import FileOAuthStateStore
78

8-
from slack_bolt import App, Say, BoltRequest
9+
from slack_bolt import App, Say, BoltRequest, BoltContext
910
from slack_bolt.authorization import AuthorizeResult
1011
from slack_bolt.error import BoltError
1112
from slack_bolt.oauth import OAuthFlow
@@ -240,3 +241,55 @@ def test_none_body_no_middleware(self):
240241
response = app.dispatch(req)
241242
assert response.status == 404
242243
assert response.body == '{"error": "unhandled request"}'
244+
245+
def test_proxy_ssl_for_respond(self):
246+
ssl = SSLContext()
247+
web_client = WebClient(
248+
token=self.valid_token,
249+
base_url=self.mock_api_server_base_url,
250+
proxy="http://proxy-host:9000/",
251+
ssl=ssl,
252+
)
253+
app = App(
254+
signing_secret="valid",
255+
client=web_client,
256+
authorize=lambda: AuthorizeResult(
257+
enterprise_id="E111",
258+
team_id="T111",
259+
),
260+
)
261+
262+
event_body = {
263+
"token": "verification_token",
264+
"team_id": "T111",
265+
"enterprise_id": "E111",
266+
"api_app_id": "A111",
267+
"event": {
268+
"client_msg_id": "9cbd4c5b-7ddf-4ede-b479-ad21fca66d63",
269+
"type": "app_mention",
270+
"text": "<@W111> Hi there!",
271+
"user": "W222",
272+
"ts": "1595926230.009600",
273+
"team": "T111",
274+
"channel": "C111",
275+
"event_ts": "1595926230.009600",
276+
},
277+
"type": "event_callback",
278+
"event_id": "Ev111",
279+
"event_time": 1595926230,
280+
}
281+
282+
result = {"called": False}
283+
284+
@app.event("app_mention")
285+
def handle(context: BoltContext, respond):
286+
assert context.respond.proxy == "http://proxy-host:9000/"
287+
assert context.respond.ssl == ssl
288+
assert respond.proxy == "http://proxy-host:9000/"
289+
assert respond.ssl == ssl
290+
result["called"] = True
291+
292+
req = BoltRequest(body=event_body, headers={}, mode="socket_mode")
293+
response = app.dispatch(req)
294+
assert response.status == 200
295+
assert result["called"] is True

tests/scenario_tests_async/test_app.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,43 @@
1+
import asyncio
2+
from ssl import SSLContext
3+
14
import pytest
25
from slack_sdk import WebClient
36
from slack_sdk.oauth.installation_store import FileInstallationStore
47
from slack_sdk.oauth.state_store import FileOAuthStateStore
8+
from slack_sdk.web.async_client import AsyncWebClient
59

610
from slack_bolt.async_app import AsyncApp
711
from slack_bolt.authorization import AuthorizeResult
12+
from slack_bolt.context.async_context import AsyncBoltContext
813
from slack_bolt.error import BoltError
914
from slack_bolt.oauth.async_oauth_flow import AsyncOAuthFlow
1015
from slack_bolt.oauth.async_oauth_settings import AsyncOAuthSettings
16+
from slack_bolt.request.async_request import AsyncBoltRequest
17+
from tests.mock_web_api_server import (
18+
setup_mock_web_api_server,
19+
cleanup_mock_web_api_server,
20+
)
1121
from tests.utils import remove_os_env_temporarily, restore_os_env
1222

1323

1424
class TestAsyncApp:
25+
signing_secret = "secret"
26+
valid_token = "xoxb-valid"
27+
mock_api_server_base_url = "http://localhost:8888"
28+
29+
@pytest.fixture
30+
def event_loop(self):
31+
old_os_env = remove_os_env_temporarily()
32+
try:
33+
setup_mock_web_api_server(self)
34+
loop = asyncio.get_event_loop()
35+
yield loop
36+
loop.close()
37+
cleanup_mock_web_api_server(self)
38+
finally:
39+
restore_os_env(old_os_env)
40+
1541
def setup_method(self):
1642
self.old_os_env = remove_os_env_temporarily()
1743

@@ -163,3 +189,61 @@ def test_installation_store_conflicts(self):
163189
installation_store=store1,
164190
)
165191
assert app.installation_store is store1
192+
193+
@pytest.mark.asyncio
194+
async def test_proxy_ssl_for_respond(self):
195+
ssl = SSLContext()
196+
web_client = AsyncWebClient(
197+
token=self.valid_token,
198+
base_url=self.mock_api_server_base_url,
199+
proxy="http://proxy-host:9000/",
200+
ssl=ssl,
201+
)
202+
203+
async def my_authorize():
204+
return AuthorizeResult(
205+
enterprise_id="E111",
206+
team_id="T111",
207+
)
208+
209+
app = AsyncApp(
210+
signing_secret="valid",
211+
client=web_client,
212+
authorize=my_authorize,
213+
)
214+
215+
event_body = {
216+
"token": "verification_token",
217+
"team_id": "T111",
218+
"enterprise_id": "E111",
219+
"api_app_id": "A111",
220+
"event": {
221+
"client_msg_id": "9cbd4c5b-7ddf-4ede-b479-ad21fca66d63",
222+
"type": "app_mention",
223+
"text": "<@W111> Hi there!",
224+
"user": "W222",
225+
"ts": "1595926230.009600",
226+
"team": "T111",
227+
"channel": "C111",
228+
"event_ts": "1595926230.009600",
229+
},
230+
"type": "event_callback",
231+
"event_id": "Ev111",
232+
"event_time": 1595926230,
233+
}
234+
235+
result = {"called": False}
236+
237+
@app.event("app_mention")
238+
async def handle(context: AsyncBoltContext, respond):
239+
assert context.respond.proxy == "http://proxy-host:9000/"
240+
assert context.respond.ssl == ssl
241+
assert respond.proxy == "http://proxy-host:9000/"
242+
assert respond.ssl == ssl
243+
result["called"] = True
244+
245+
req = AsyncBoltRequest(body=event_body, headers={}, mode="socket_mode")
246+
response = await app.async_dispatch(req)
247+
assert response.status == 200
248+
await asyncio.sleep(0.5) # wait a bit after auto ack()
249+
assert result["called"] is True

0 commit comments

Comments
 (0)