Skip to content

Commit 5033ae4

Browse files
committed
Add OAuth related arg validation and flexibility
1 parent fa5a264 commit 5033ae4

File tree

7 files changed

+84
-7
lines changed

7 files changed

+84
-7
lines changed

slack_bolt/app/async_app.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
error_listener_function_must_be_coro_func,
3535
error_client_invalid_type_async,
3636
error_authorize_conflicts,
37+
error_oauth_settings_invalid_type_async,
38+
error_oauth_flow_invalid_type_async,
3739
)
3840
from slack_bolt.lazy_listener.asyncio_runner import AsyncioLazyListenerRunner
3941
from slack_bolt.listener.async_listener import AsyncListener, AsyncCustomListener
@@ -167,6 +169,9 @@ def __init__(
167169
oauth_settings = AsyncOAuthSettings()
168170

169171
if oauth_flow:
172+
if not isinstance(oauth_flow, AsyncOAuthFlow):
173+
raise BoltError(error_oauth_flow_invalid_type_async())
174+
170175
self._async_oauth_flow = oauth_flow
171176
installation_store = select_consistent_installation_store(
172177
client_id=self._async_oauth_flow.client_id,
@@ -182,6 +187,9 @@ def __init__(
182187
if self._async_authorize is None:
183188
self._async_authorize = self._async_oauth_flow.settings.authorize
184189
elif oauth_settings is not None:
190+
if not isinstance(oauth_settings, AsyncOAuthSettings):
191+
raise BoltError(error_oauth_settings_invalid_type_async())
192+
185193
installation_store = select_consistent_installation_store(
186194
client_id=oauth_settings.client_id,
187195
app_store=self._async_installation_store,

slack_bolt/logger/messages.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@ def error_client_invalid_type_async() -> str:
2626
return "`client` must be a slack_sdk.web.async_client.AsyncWebClient"
2727

2828

29+
def error_oauth_flow_invalid_type_async() -> str:
30+
return "`oauth_flow` must be a slack_bolt.oauth.async_oauth_flow.AsyncOAuthFlow"
31+
32+
33+
def error_oauth_settings_invalid_type_async() -> str:
34+
return "`oauth_settings` must be a slack_bolt.oauth.async_oauth_settings.AsyncOAuthSettings"
35+
36+
2937
def error_auth_test_failure(error_response: SlackResponse) -> str:
3038
return f"`token` is invalid (auth.test result: {error_response})"
3139

slack_bolt/oauth/async_oauth_flow.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from typing import Optional, Dict, Callable, Awaitable, Sequence
55

66
from slack_bolt.error import BoltError
7+
from slack_bolt.logger.messages import error_oauth_settings_invalid_type_async
78
from slack_bolt.oauth.async_callback_options import (
89
AsyncCallbackOptions,
910
DefaultAsyncCallbackOptions,
@@ -62,7 +63,11 @@ def __init__(
6263
"""
6364
self._async_client = client
6465
self._logger = logger
66+
67+
if not isinstance(settings, AsyncOAuthSettings):
68+
raise BoltError(error_oauth_settings_invalid_type_async())
6569
self.settings = settings
70+
6671
self.settings.logger = self._logger
6772

6873
self.client_id = self.settings.client_id

slack_bolt/oauth/async_oauth_settings.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import logging
22
import os
33
from logging import Logger
4-
from typing import Optional, Sequence
4+
from typing import Optional, Sequence, Union
55

66
from slack_sdk.oauth import (
77
OAuthStateUtils,
@@ -57,8 +57,8 @@ def __init__(
5757
# OAuth flow parameters/credentials
5858
client_id: Optional[str] = None, # required
5959
client_secret: Optional[str] = None, # required
60-
scopes: Optional[Sequence[str]] = None,
61-
user_scopes: Optional[Sequence[str]] = None,
60+
scopes: Optional[Union[Sequence[str], str]] = None,
61+
user_scopes: Optional[Union[Sequence[str], str]] = None,
6262
redirect_uri: Optional[str] = None,
6363
# Handler configuration
6464
install_path: str = "/slack/install",
@@ -104,9 +104,13 @@ def __init__(
104104
raise BoltError("Both client_id and client_secret are required")
105105

106106
self.scopes = scopes or os.environ.get("SLACK_SCOPES", "").split(",")
107+
if isinstance(self.scopes, str):
108+
self.scopes = self.scopes.split(",")
107109
self.user_scopes = user_scopes or os.environ.get("SLACK_USER_SCOPES", "").split(
108110
","
109111
)
112+
if isinstance(self.user_scopes, str):
113+
self.user_scopes = self.user_scopes.split(",")
110114
self.redirect_uri = redirect_uri or os.environ.get("SLACK_REDIRECT_URI")
111115
# Handler configuration
112116
self.install_path = install_path or os.environ.get(

slack_bolt/oauth/oauth_settings.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import logging
22
import os
33
from logging import Logger
4-
from typing import Optional, Sequence
4+
from typing import Optional, Sequence, Union
55

66
from slack_sdk.oauth import (
77
OAuthStateStore,
@@ -52,8 +52,8 @@ def __init__(
5252
# OAuth flow parameters/credentials
5353
client_id: Optional[str] = None, # required
5454
client_secret: Optional[str] = None, # required
55-
scopes: Optional[Sequence[str]] = None,
56-
user_scopes: Optional[Sequence[str]] = None,
55+
scopes: Optional[Union[Sequence[str], str]] = None,
56+
user_scopes: Optional[Union[Sequence[str], str]] = None,
5757
redirect_uri: Optional[str] = None,
5858
# Handler configuration
5959
install_path: str = "/slack/install",
@@ -98,9 +98,13 @@ def __init__(
9898
raise BoltError("Both client_id and client_secret are required")
9999

100100
self.scopes = scopes or os.environ.get("SLACK_SCOPES", "").split(",")
101+
if isinstance(self.scopes, str):
102+
self.scopes = self.scopes.split(",")
101103
self.user_scopes = user_scopes or os.environ.get("SLACK_USER_SCOPES", "").split(
102104
","
103105
)
106+
if isinstance(self.user_scopes, str):
107+
self.user_scopes = self.user_scopes.split(",")
104108
self.redirect_uri = redirect_uri or os.environ.get("SLACK_REDIRECT_URI")
105109
# Handler configuration
106110
self.install_path = install_path or os.environ.get(

tests/slack_bolt/oauth/test_oauth_flow.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ def test_handle_installation(self):
4646
client_id="111.222",
4747
client_secret="xxx",
4848
scopes=["chat:write", "commands"],
49+
user_scopes=["search:read"],
4950
installation_store=FileInstallationStore(),
5051
state_store=FileOAuthStateStore(expiration_seconds=120),
5152
)
@@ -54,9 +55,19 @@ def test_handle_installation(self):
5455
resp = oauth_flow.handle_installation(req)
5556
assert resp.status == 200
5657
assert resp.headers.get("content-type") == ["text/html; charset=utf-8"]
57-
assert resp.headers.get("content-length") == ["565"]
58+
assert resp.headers.get("content-length") == ["576"]
5859
assert "https://slack.com/oauth/v2/authorize?state=" in resp.body
5960

61+
def test_scopes_as_str(self):
62+
settings = OAuthSettings(
63+
client_id="111.222",
64+
client_secret="xxx",
65+
scopes="chat:write,commands",
66+
user_scopes="search:read",
67+
)
68+
assert settings.scopes == ["chat:write", "commands"]
69+
assert settings.user_scopes == ["search:read"]
70+
6071
def test_handle_callback(self):
6172
oauth_flow = OAuthFlow(
6273
client=WebClient(base_url=self.mock_api_server_base_url),

tests/slack_bolt_async/oauth/test_async_oauth_flow.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@
1111

1212
from slack_bolt import BoltResponse
1313
from slack_bolt.app.async_app import AsyncApp
14+
from slack_bolt.error import BoltError
1415
from slack_bolt.oauth.async_callback_options import (
1516
AsyncFailureArgs,
1617
AsyncSuccessArgs,
1718
AsyncCallbackOptions,
1819
)
1920
from slack_bolt.oauth.async_oauth_flow import AsyncOAuthFlow
2021
from slack_bolt.oauth.async_oauth_settings import AsyncOAuthSettings
22+
from slack_bolt.oauth.oauth_settings import OAuthSettings
2123
from slack_bolt.request.async_request import AsyncBoltRequest
2224
from tests.mock_web_api_server import (
2325
cleanup_mock_web_api_server,
@@ -46,6 +48,7 @@ async def test_instantiation(self):
4648
client_id="111.222",
4749
client_secret="xxx",
4850
scopes=["chat:write", "commands"],
51+
user_scopes=["search:read"],
4952
installation_store=FileInstallationStore(),
5053
state_store=FileOAuthStateStore(expiration_seconds=120),
5154
)
@@ -54,6 +57,40 @@ async def test_instantiation(self):
5457
assert oauth_flow.logger is not None
5558
assert oauth_flow.client is not None
5659

60+
@pytest.mark.asyncio
61+
async def test_scopes_as_str(self):
62+
settings = AsyncOAuthSettings(
63+
client_id="111.222",
64+
client_secret="xxx",
65+
scopes="chat:write,commands",
66+
user_scopes="search:read",
67+
)
68+
assert settings.scopes == ["chat:write", "commands"]
69+
assert settings.user_scopes == ["search:read"]
70+
71+
@pytest.mark.asyncio
72+
async def test_instantiation_non_async_settings(self):
73+
with pytest.raises(BoltError):
74+
AsyncOAuthFlow(
75+
settings=OAuthSettings(
76+
client_id="111.222",
77+
client_secret="xxx",
78+
scopes="chat:write,commands",
79+
)
80+
)
81+
82+
@pytest.mark.asyncio
83+
async def test_instantiation_non_async_settings_to_app(self):
84+
with pytest.raises(BoltError):
85+
AsyncApp(
86+
signing_secret="xxx",
87+
oauth_settings=OAuthSettings(
88+
client_id="111.222",
89+
client_secret="xxx",
90+
scopes="chat:write,commands",
91+
),
92+
)
93+
5794
@pytest.mark.asyncio
5895
async def test_handle_installation(self):
5996
oauth_flow = AsyncOAuthFlow(

0 commit comments

Comments
 (0)