Skip to content

Commit 48dd31e

Browse files
authored
Add before_authorize middleware (#869)
1 parent 12aae7f commit 48dd31e

File tree

4 files changed

+163
-16
lines changed

4 files changed

+163
-16
lines changed

slack_bolt/app/app.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ def __init__(
101101
token_verification_enabled: bool = True,
102102
client: Optional[WebClient] = None,
103103
# for multi-workspace apps
104+
before_authorize: Optional[Union[Middleware, Callable[..., Any]]] = None,
104105
authorize: Optional[Callable[..., AuthorizeResult]] = None,
105106
installation_store: Optional[InstallationStore] = None,
106107
# for either only bot scope usage or v1.0.x compatibility
@@ -155,6 +156,7 @@ def message_hello(message, say):
155156
token: The bot/user access token required only for single-workspace app.
156157
token_verification_enabled: Verifies the validity of the given token if True.
157158
client: The singleton `slack_sdk.WebClient` instance for this app.
159+
before_authorize: A global middleware that can be executed right before authorize function
158160
authorize: The function to authorize an incoming request from Slack
159161
by checking if there is a team/user in the installation data.
160162
installation_store: The module offering save/find operations of installation data
@@ -215,6 +217,17 @@ def message_hello(message, say):
215217
# Authorize & OAuthFlow initialization
216218
# --------------------------------------
217219

220+
self._before_authorize: Optional[Middleware] = None
221+
if before_authorize is not None:
222+
if isinstance(before_authorize, Callable):
223+
self._before_authorize = CustomMiddleware(
224+
app_name=self._name,
225+
func=before_authorize,
226+
base_logger=self._framework_logger,
227+
)
228+
elif isinstance(before_authorize, Middleware):
229+
self._before_authorize = before_authorize
230+
218231
self._authorize: Optional[Authorize] = None
219232
if authorize is not None:
220233
if isinstance(authorize, Authorize):
@@ -357,6 +370,9 @@ def _init_middleware_list(
357370
if request_verification_enabled is True:
358371
self._middleware_list.append(RequestVerification(self._signing_secret, base_logger=self._base_logger))
359372

373+
if self._before_authorize is not None:
374+
self._middleware_list.append(self._before_authorize)
375+
360376
# As authorize is required for making a Bolt app function, we don't offer the flag to disable this
361377
if self._oauth_flow is None:
362378
if self._token is not None:

slack_bolt/app/async_app.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import logging
33
import os
44
import time
5-
from typing import Optional, List, Union, Callable, Pattern, Dict, Awaitable, Sequence
5+
from typing import Optional, List, Union, Callable, Pattern, Dict, Awaitable, Sequence, Any
66

77
from aiohttp import web
88

@@ -112,6 +112,7 @@ def __init__(
112112
token: Optional[str] = None,
113113
client: Optional[AsyncWebClient] = None,
114114
# for multi-workspace apps
115+
before_authorize: Optional[Union[AsyncMiddleware, Callable[..., Awaitable[Any]]]] = None,
115116
authorize: Optional[Callable[..., Awaitable[AuthorizeResult]]] = None,
116117
installation_store: Optional[AsyncInstallationStore] = None,
117118
# for either only bot scope usage or v1.0.x compatibility
@@ -163,6 +164,7 @@ async def message_hello(message, say): # async function
163164
signing_secret: The Signing Secret value used for verifying requests from Slack.
164165
token: The bot/user access token required only for single-workspace app.
165166
client: The singleton `slack_sdk.web.async_client.AsyncWebClient` instance for this app.
167+
before_authorize: A global middleware that can be executed right before authorize function
166168
authorize: The function to authorize an incoming request from Slack
167169
by checking if there is a team/user in the installation data.
168170
installation_store: The module offering save/find operations of installation data
@@ -220,6 +222,17 @@ async def message_hello(message, say): # async function
220222
# Authorize & OAuthFlow initialization
221223
# --------------------------------------
222224

225+
self._async_before_authorize: Optional[AsyncMiddleware] = None
226+
if before_authorize is not None:
227+
if isinstance(before_authorize, Callable):
228+
self._async_before_authorize = AsyncCustomMiddleware(
229+
app_name=self._name,
230+
func=before_authorize,
231+
base_logger=self._framework_logger,
232+
)
233+
elif isinstance(before_authorize, AsyncMiddleware):
234+
self._async_before_authorize = before_authorize
235+
223236
self._async_authorize: Optional[AsyncAuthorize] = None
224237
if authorize is not None:
225238
if isinstance(authorize, AsyncAuthorize):
@@ -363,6 +376,10 @@ def _init_async_middleware_list(
363376
)
364377
if request_verification_enabled is True:
365378
self._async_middleware_list.append(AsyncRequestVerification(self._signing_secret, base_logger=self._base_logger))
379+
380+
if self._async_before_authorize is not None:
381+
self._async_middleware_list.append(self._async_before_authorize)
382+
366383
# As authorize is required for making a Bolt app function, we don't offer the flag to disable this
367384
if self._async_oauth_flow is None:
368385
if self._token:

tests/scenario_tests/test_authorize.py

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
from slack_sdk import WebClient
66
from slack_sdk.signature import SignatureVerifier
77

8-
from slack_bolt import BoltRequest
8+
from slack_bolt import BoltRequest, BoltResponse
99
from slack_bolt.app import App
1010
from slack_bolt.authorization import AuthorizeResult
11+
from slack_bolt.request.payload_utils import is_event
1112
from tests.mock_web_api_server import (
1213
setup_mock_web_api_server,
1314
cleanup_mock_web_api_server,
@@ -78,8 +79,37 @@ def build_headers(self, timestamp: str, body: str):
7879
"x-slack-request-timestamp": [timestamp],
7980
}
8081

81-
def build_valid_request(self) -> BoltRequest:
82+
def build_block_actions_request(self) -> BoltRequest:
8283
timestamp = str(int(time()))
84+
return BoltRequest(body=block_actions_raw_body, headers=self.build_headers(timestamp, block_actions_raw_body))
85+
86+
def build_message_changed_event_request(self) -> BoltRequest:
87+
timestamp = str(int(time()))
88+
raw_body = json.dumps(
89+
{
90+
"token": "verification_token",
91+
"team_id": "T111",
92+
"enterprise_id": "E111",
93+
"api_app_id": "A111",
94+
"event": {
95+
"type": "message",
96+
"subtype": "message_changed",
97+
"channel": "C2147483705",
98+
"ts": "1358878755.000001",
99+
"message": {
100+
"type": "message",
101+
"user": "U2147483697",
102+
"text": "Hello, world!",
103+
"ts": "1355517523.000005",
104+
"edited": {"user": "U2147483697", "ts": "1358878755.000001"},
105+
},
106+
},
107+
"type": "event_callback",
108+
"event_id": "Ev111",
109+
"event_time": 1599616881,
110+
"authed_users": ["W111"],
111+
}
112+
)
83113
return BoltRequest(body=raw_body, headers=self.build_headers(timestamp, raw_body))
84114

85115
def test_success(self):
@@ -90,7 +120,7 @@ def test_success(self):
90120
)
91121
app.action("a")(simple_listener)
92122

93-
request = self.build_valid_request()
123+
request = self.build_block_actions_request()
94124
response = app.dispatch(request)
95125
assert response.status == 200
96126
assert response.body == ""
@@ -104,7 +134,7 @@ def test_failure(self):
104134
)
105135
app.action("a")(simple_listener)
106136

107-
request = self.build_valid_request()
137+
request = self.build_block_actions_request()
108138
response = app.dispatch(request)
109139
assert response.status == 200
110140
assert response.body == ""
@@ -118,7 +148,7 @@ def test_bot_context_attributes(self):
118148
)
119149
app.action("a")(assert_bot_context_attributes)
120150

121-
request = self.build_valid_request()
151+
request = self.build_block_actions_request()
122152
response = app.dispatch(request)
123153
assert response.status == 200
124154
assert response.body == ""
@@ -132,14 +162,40 @@ def test_user_context_attributes(self):
132162
)
133163
app.action("a")(assert_user_context_attributes)
134164

135-
request = self.build_valid_request()
165+
request = self.build_block_actions_request()
136166
response = app.dispatch(request)
137167
assert response.status == 200
138168
assert response.body == ""
139169
assert_auth_test_count(self, 1)
140170

171+
def test_before_authorize(self):
172+
def skip_message_changed_events(body: dict, payload: dict, next_):
173+
if is_event(body) and payload.get("type") == "message" and payload.get("subtype") == "message_changed":
174+
return BoltResponse(status=200, body="as expected")
175+
next_()
176+
177+
app = App(
178+
client=self.web_client,
179+
before_authorize=skip_message_changed_events,
180+
authorize=user_authorize,
181+
signing_secret=self.signing_secret,
182+
)
183+
app.action("a")(assert_user_context_attributes)
184+
185+
request = self.build_block_actions_request()
186+
response = app.dispatch(request)
187+
assert response.status == 200
188+
assert response.body == ""
189+
assert_auth_test_count(self, 1)
190+
191+
request = self.build_message_changed_event_request()
192+
response = app.dispatch(request)
193+
assert response.status == 200
194+
assert response.body == "as expected"
195+
assert_auth_test_count(self, 1) # should be skipped
196+
141197

142-
body = {
198+
block_actions_body = {
143199
"type": "block_actions",
144200
"user": {
145201
"id": "W99999",
@@ -176,7 +232,7 @@ def test_user_context_attributes(self):
176232
],
177233
}
178234

179-
raw_body = f"payload={quote(json.dumps(body))}"
235+
block_actions_raw_body = f"payload={quote(json.dumps(block_actions_body))}"
180236

181237

182238
def simple_listener(ack, body, payload, action):

tests/scenario_tests_async/test_authorize.py

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
from slack_sdk.signature import SignatureVerifier
88
from slack_sdk.web.async_client import AsyncWebClient
99

10+
from slack_bolt import BoltResponse
1011
from slack_bolt.app.async_app import AsyncApp
1112
from slack_bolt.authorization import AuthorizeResult
1213
from slack_bolt.request.async_request import AsyncBoltRequest
14+
from slack_bolt.request.payload_utils import is_event
1315
from tests.mock_web_api_server import (
1416
setup_mock_web_api_server,
1517
cleanup_mock_web_api_server,
@@ -84,8 +86,37 @@ def build_headers(self, timestamp: str, body: str):
8486
"x-slack-request-timestamp": [timestamp],
8587
}
8688

87-
def build_valid_request(self) -> AsyncBoltRequest:
89+
def build_block_actions_request(self) -> AsyncBoltRequest:
8890
timestamp = str(int(time()))
91+
return AsyncBoltRequest(body=block_actions_raw_body, headers=self.build_headers(timestamp, block_actions_raw_body))
92+
93+
def build_message_changed_event_request(self) -> AsyncBoltRequest:
94+
timestamp = str(int(time()))
95+
raw_body = json.dumps(
96+
{
97+
"token": "verification_token",
98+
"team_id": "T111",
99+
"enterprise_id": "E111",
100+
"api_app_id": "A111",
101+
"event": {
102+
"type": "message",
103+
"subtype": "message_changed",
104+
"channel": "C2147483705",
105+
"ts": "1358878755.000001",
106+
"message": {
107+
"type": "message",
108+
"user": "U2147483697",
109+
"text": "Hello, world!",
110+
"ts": "1355517523.000005",
111+
"edited": {"user": "U2147483697", "ts": "1358878755.000001"},
112+
},
113+
},
114+
"type": "event_callback",
115+
"event_id": "Ev111",
116+
"event_time": 1599616881,
117+
"authed_users": ["W111"],
118+
}
119+
)
89120
return AsyncBoltRequest(body=raw_body, headers=self.build_headers(timestamp, raw_body))
90121

91122
@pytest.mark.asyncio
@@ -97,7 +128,7 @@ async def test_success(self):
97128
)
98129
app.action("a")(simple_listener)
99130

100-
request = self.build_valid_request()
131+
request = self.build_block_actions_request()
101132
response = await app.async_dispatch(request)
102133
assert response.status == 200
103134
assert response.body == ""
@@ -112,7 +143,7 @@ async def test_failure(self):
112143
)
113144
app.block_action("a")(simple_listener)
114145

115-
request = self.build_valid_request()
146+
request = self.build_block_actions_request()
116147
response = await app.async_dispatch(request)
117148
assert response.status == 200
118149
assert response.body == ""
@@ -127,7 +158,7 @@ async def test_bot_context_attributes(self):
127158
)
128159
app.action("a")(assert_bot_context_attributes)
129160

130-
request = self.build_valid_request()
161+
request = self.build_block_actions_request()
131162
response = await app.async_dispatch(request)
132163
assert response.status == 200
133164
assert response.body == ""
@@ -142,14 +173,41 @@ async def test_user_context_attributes(self):
142173
)
143174
app.action("a")(assert_user_context_attributes)
144175

145-
request = self.build_valid_request()
176+
request = self.build_block_actions_request()
146177
response = await app.async_dispatch(request)
147178
assert response.status == 200
148179
assert response.body == ""
149180
await assert_auth_test_count_async(self, 1)
150181

182+
@pytest.mark.asyncio
183+
async def test_user_context_attributes(self):
184+
async def skip_message_changed_events(body: dict, payload: dict, next_):
185+
if is_event(body) and payload.get("type") == "message" and payload.get("subtype") == "message_changed":
186+
return BoltResponse(status=200, body="as expected")
187+
await next_()
188+
189+
app = AsyncApp(
190+
client=self.web_client,
191+
before_authorize=skip_message_changed_events,
192+
authorize=user_authorize,
193+
signing_secret=self.signing_secret,
194+
)
195+
app.action("a")(assert_user_context_attributes)
196+
197+
request = self.build_block_actions_request()
198+
response = await app.async_dispatch(request)
199+
assert response.status == 200
200+
assert response.body == ""
201+
await assert_auth_test_count_async(self, 1)
202+
203+
request = self.build_message_changed_event_request()
204+
response = await app.async_dispatch(request)
205+
assert response.status == 200
206+
assert response.body == "as expected"
207+
await assert_auth_test_count_async(self, 1) # should be skipped
208+
151209

152-
body = {
210+
block_actions_body = {
153211
"type": "block_actions",
154212
"user": {
155213
"id": "W99999",
@@ -186,7 +244,7 @@ async def test_user_context_attributes(self):
186244
],
187245
}
188246

189-
raw_body = f"payload={quote(json.dumps(body))}"
247+
block_actions_raw_body = f"payload={quote(json.dumps(block_actions_body))}"
190248

191249

192250
async def simple_listener(ack, body, payload, action):

0 commit comments

Comments
 (0)