Skip to content

Commit 0d94dc8

Browse files
committed
fix(oauth): async OAuth callback crashed on org-wide installs without a bot token
An org-wide install of an app that only asks for user scopes returns no access_token from oauth.v2.access. The async flow read auth_test outside the bot-token branch, so it raised UnboundLocalError and the installation was never saved. The sync flow already nested that check correctly. - Only read the enterprise URL from auth.test when a bot token exists - Add sync and async tests for an org-wide install without a bot token
1 parent 698687c commit 0d94dc8

3 files changed

Lines changed: 69 additions & 2 deletions

File tree

‎slack_bolt/oauth/async_oauth_flow.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -331,8 +331,8 @@ async def run_installation(self, code: str) -> Optional[Installation]:
331331
if bot_token is not None:
332332
auth_test = await self.client.auth_test(token=bot_token)
333333
bot_id = auth_test["bot_id"]
334-
if is_enterprise_install is True:
335-
enterprise_url = auth_test.get("url")
334+
if is_enterprise_install is True:
335+
enterprise_url = auth_test.get("url")
336336

337337
return Installation(
338338
app_id=oauth_response.get("app_id"),

‎tests/slack_bolt/oauth/test_oauth_flow.py‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,3 +287,36 @@ def failure(args: FailureArgs) -> BoltResponse:
287287
resp = oauth_flow.handle_callback(req)
288288
assert resp.status == 502
289289
assert resp.body == "customized"
290+
291+
def test_run_installation_org_wide_install_without_bot_token(self):
292+
# An org-wide install of an app that only asks for user scopes
293+
# returns no access_token, so auth.test must not be called
294+
oauth_flow = OAuthFlow(
295+
client=WebClient(base_url=self.mock_api_server_base_url),
296+
settings=OAuthSettings(
297+
client_id="111.222",
298+
client_secret="xxx",
299+
scopes=["chat:write", "commands"],
300+
installation_store=FileInstallationStore(),
301+
state_store=FileOAuthStateStore(expiration_seconds=120),
302+
),
303+
)
304+
305+
def oauth_v2_access(**kwargs):
306+
return {
307+
"ok": True,
308+
"app_id": "A111",
309+
"is_enterprise_install": True,
310+
"enterprise": {"id": "E111", "name": "Org"},
311+
"team": None,
312+
"authed_user": {"id": "U111", "scope": "search:read", "access_token": "xoxp-1234", "token_type": "user"},
313+
}
314+
315+
oauth_flow.client.oauth_v2_access = oauth_v2_access
316+
317+
installation = oauth_flow.run_installation("code")
318+
assert installation is not None
319+
assert installation.is_enterprise_install is True
320+
assert installation.enterprise_id == "E111"
321+
assert installation.bot_token is None
322+
assert installation.user_token == "xoxp-1234"

‎tests/slack_bolt_async/oauth/test_async_oauth_flow.py‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,3 +334,37 @@ async def failure(args: AsyncFailureArgs) -> BoltResponse:
334334
resp = await oauth_flow.handle_callback(req)
335335
assert resp.status == 502
336336
assert resp.body == "customized"
337+
338+
@pytest.mark.asyncio
339+
async def test_run_installation_org_wide_install_without_bot_token(self):
340+
# An org-wide install of an app that only asks for user scopes
341+
# returns no access_token, so auth.test must not be called
342+
oauth_flow = AsyncOAuthFlow(
343+
client=AsyncWebClient(base_url=self.mock_api_server_base_url),
344+
settings=AsyncOAuthSettings(
345+
client_id="111.222",
346+
client_secret="xxx",
347+
scopes=["chat:write", "commands"],
348+
installation_store=FileInstallationStore(),
349+
state_store=FileOAuthStateStore(expiration_seconds=120),
350+
),
351+
)
352+
353+
async def oauth_v2_access(**kwargs):
354+
return {
355+
"ok": True,
356+
"app_id": "A111",
357+
"is_enterprise_install": True,
358+
"enterprise": {"id": "E111", "name": "Org"},
359+
"team": None,
360+
"authed_user": {"id": "U111", "scope": "search:read", "access_token": "xoxp-1234", "token_type": "user"},
361+
}
362+
363+
oauth_flow.client.oauth_v2_access = oauth_v2_access
364+
365+
installation = await oauth_flow.run_installation("code")
366+
assert installation is not None
367+
assert installation.is_enterprise_install is True
368+
assert installation.enterprise_id == "E111"
369+
assert installation.bot_token is None
370+
assert installation.user_token == "xoxp-1234"

0 commit comments

Comments
 (0)