Skip to content

Commit 4c22a28

Browse files
authored
Fix context.team_id for view interactions in a Slack Connect channel (#734)
1 parent 0ca2874 commit 4c22a28

File tree

4 files changed

+142
-7
lines changed

4 files changed

+142
-7
lines changed

slack_bolt/request/internals.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ def extract_enterprise_id(payload: Dict[str, Any]) -> Optional[str]:
7373

7474

7575
def extract_team_id(payload: Dict[str, Any]) -> Optional[str]:
76+
if payload.get("view", {}).get("app_installed_team_id") is not None:
77+
# view_submission payloads can have `view.app_installed_team_id` when a modal view that was opened
78+
# in a different workspace via some operations inside a Slack Connect channel.
79+
# Note that the same for enterprise_id does not exist. When you need to know the enterprise_id as well,
80+
# you have to run some query toward your InstallationStore to know the org where the team_id belongs to.
81+
return payload.get("view")["app_installed_team_id"]
7682
if payload.get("team") is not None:
7783
# With org-wide installations, payload.team in interactivity payloads can be None
7884
# You need to extract either payload.user.team_id or payload.view.team_id as below

tests/scenario_tests/test_view_submission.py

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
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, BoltContext
99
from slack_bolt.app import App
1010
from tests.mock_web_api_server import (
1111
setup_mock_web_api_server,
@@ -140,6 +140,58 @@ def simple_listener(ack, body, payload, view):
140140
raw_response_url_body = f"payload={quote(json.dumps(response_url_payload_body))}"
141141

142142

143+
connect_channel_payload = {
144+
"type": "view_submission",
145+
"team": {
146+
"id": "T-other-side",
147+
"domain": "other-side",
148+
"enterprise_id": "E-other-side",
149+
"enterprise_name": "Kaz Sandbox Org",
150+
},
151+
"user": {"id": "W111", "username": "kaz", "name": "kaz", "team_id": "T-other-side"},
152+
"api_app_id": "A1111",
153+
"token": "legacy-fixed-token",
154+
"trigger_id": "111.222.xxx",
155+
"view": {
156+
"id": "V11111",
157+
"team_id": "T-other-side",
158+
"type": "modal",
159+
"blocks": [
160+
{
161+
"type": "input",
162+
"block_id": "zniAM",
163+
"label": {"type": "plain_text", "text": "Label"},
164+
"element": {
165+
"type": "plain_text_input",
166+
"dispatch_action_config": {"trigger_actions_on": ["on_enter_pressed"]},
167+
"action_id": "qEJr",
168+
},
169+
}
170+
],
171+
"private_metadata": "",
172+
"callback_id": "view-id",
173+
"state": {"values": {"zniAM": {"qEJr": {"type": "plain_text_input", "value": "Hi there!"}}}},
174+
"hash": "1664950703.CmTS8F7U",
175+
"title": {"type": "plain_text", "text": "My App"},
176+
"close": {"type": "plain_text", "text": "Cancel"},
177+
"submit": {"type": "plain_text", "text": "Submit"},
178+
"root_view_id": "V00000",
179+
"app_id": "A1111",
180+
"external_id": "",
181+
"app_installed_team_id": "T-installed-workspace",
182+
"bot_id": "B1111",
183+
},
184+
"enterprise": {"id": "E-other-side", "name": "Kaz Sandbox Org"},
185+
}
186+
187+
connect_channel_body = f"payload={quote(json.dumps(connect_channel_payload))}"
188+
189+
190+
def verify_connected_channel(ack, context: BoltContext):
191+
assert context.team_id == "T-installed-workspace"
192+
ack()
193+
194+
143195
class TestViewSubmission:
144196
signing_secret = "secret"
145197
valid_token = "xoxb-valid"
@@ -261,3 +313,15 @@ def check(ack, respond):
261313
response = app.dispatch(request)
262314
assert response.status == 200
263315
assert_auth_test_count(self, 1)
316+
317+
def test_connected_channels(self):
318+
app = App(
319+
client=self.web_client,
320+
signing_secret=self.signing_secret,
321+
)
322+
app.view("view-id")(verify_connected_channel)
323+
324+
request = self.build_valid_request(body=connect_channel_body)
325+
response = app.dispatch(request)
326+
assert response.status == 200
327+
assert_auth_test_count(self, 1)

tests/scenario_tests_async/test_view_submission.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,53 @@ def simple_listener(ack, body, payload, view):
142142
raw_response_url_body = f"payload={quote(json.dumps(response_url_payload_body))}"
143143

144144

145+
connect_channel_payload = {
146+
"type": "view_submission",
147+
"team": {
148+
"id": "T-other-side",
149+
"domain": "other-side",
150+
"enterprise_id": "E-other-side",
151+
"enterprise_name": "Kaz Sandbox Org",
152+
},
153+
"user": {"id": "W111", "username": "kaz", "name": "kaz", "team_id": "T-other-side"},
154+
"api_app_id": "A1111",
155+
"token": "legacy-fixed-token",
156+
"trigger_id": "111.222.xxx",
157+
"view": {
158+
"id": "V11111",
159+
"team_id": "T-other-side",
160+
"type": "modal",
161+
"blocks": [
162+
{
163+
"type": "input",
164+
"block_id": "zniAM",
165+
"label": {"type": "plain_text", "text": "Label"},
166+
"element": {
167+
"type": "plain_text_input",
168+
"dispatch_action_config": {"trigger_actions_on": ["on_enter_pressed"]},
169+
"action_id": "qEJr",
170+
},
171+
}
172+
],
173+
"private_metadata": "",
174+
"callback_id": "view-id",
175+
"state": {"values": {"zniAM": {"qEJr": {"type": "plain_text_input", "value": "Hi there!"}}}},
176+
"hash": "1664950703.CmTS8F7U",
177+
"title": {"type": "plain_text", "text": "My App"},
178+
"close": {"type": "plain_text", "text": "Cancel"},
179+
"submit": {"type": "plain_text", "text": "Submit"},
180+
"root_view_id": "V00000",
181+
"app_id": "A1111",
182+
"external_id": "",
183+
"app_installed_team_id": "T-installed-workspace",
184+
"bot_id": "B1111",
185+
},
186+
"enterprise": {"id": "E-other-side", "name": "Kaz Sandbox Org"},
187+
}
188+
189+
connect_channel_body = f"payload={quote(json.dumps(connect_channel_payload))}"
190+
191+
145192
class TestAsyncViewSubmission:
146193
signing_secret = "secret"
147194
valid_token = "xoxb-valid"
@@ -275,10 +322,28 @@ async def check(ack, respond):
275322
assert response.status == 200
276323
await assert_auth_test_count_async(self, 1)
277324

325+
@pytest.mark.asyncio
326+
async def test_connected_channels(self):
327+
app = AsyncApp(
328+
client=self.web_client,
329+
signing_secret=self.signing_secret,
330+
)
331+
app.view("view-id")(verify_connected_channel)
332+
333+
request = self.build_valid_request(body=connect_channel_body)
334+
response = await app.async_dispatch(request)
335+
assert response.status == 200
336+
await assert_auth_test_count_async(self, 1)
337+
278338

279339
async def simple_listener(ack, body, payload, view):
280340
assert body["trigger_id"] == "111.222.valid"
281341
assert body["view"] == payload
282342
assert payload == view
283343
assert view["private_metadata"] == "This is for you!"
284344
await ack()
345+
346+
347+
async def verify_connected_channel(ack, context):
348+
assert context.team_id == "T-installed-workspace"
349+
await ack()

tests/slack_bolt/request/test_request.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,14 +84,14 @@ def test_org_wide_installations_view_submission(self):
8484
"id": "W111",
8585
"username": "primary-owner",
8686
"name": "primary-owner",
87-
"team_id": "T_expected"
87+
"team_id": "T_unexpected"
8888
},
8989
"api_app_id": "A111",
9090
"token": "fixed-value",
9191
"trigger_id": "1111.222.xxx",
9292
"view": {
9393
"id": "V111",
94-
"team_id": "T_expected",
94+
"team_id": "T_unexpected",
9595
"type": "modal",
9696
"blocks": [
9797
{
@@ -147,7 +147,7 @@ def test_org_wide_installations_view_submission(self):
147147
"root_view_id": "V111",
148148
"app_id": "A111",
149149
"external_id": "",
150-
"app_installed_team_id": "E111",
150+
"app_installed_team_id": "T_expected",
151151
"bot_id": "B111"
152152
},
153153
"response_urls": [],
@@ -172,13 +172,13 @@ def test_org_wide_installations_view_closed(self):
172172
"id": "W111",
173173
"username": "primary-owner",
174174
"name": "primary-owner",
175-
"team_id": "T_expected"
175+
"team_id": "T_unexpected"
176176
},
177177
"api_app_id": "A111",
178178
"token": "fixed-value",
179179
"view": {
180180
"id": "V111",
181-
"team_id": "T_expected",
181+
"team_id": "T_unexpected",
182182
"type": "modal",
183183
"blocks": [
184184
{
@@ -225,7 +225,7 @@ def test_org_wide_installations_view_closed(self):
225225
"root_view_id": "V111",
226226
"app_id": "A111",
227227
"external_id": "",
228-
"app_installed_team_id": "E111",
228+
"app_installed_team_id": "T_expected",
229229
"bot_id": "B0302M47727"
230230
},
231231
"is_cleared": false,

0 commit comments

Comments
 (0)