Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions slack_bolt/app/async_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
error_listener_function_must_be_coro_func,
error_client_invalid_type_async,
error_authorize_conflicts,
error_oauth_settings_invalid_type_async,
error_oauth_flow_invalid_type_async,
)
from slack_bolt.lazy_listener.asyncio_runner import AsyncioLazyListenerRunner
from slack_bolt.listener.async_listener import AsyncListener, AsyncCustomListener
Expand Down Expand Up @@ -167,6 +169,9 @@ def __init__(
oauth_settings = AsyncOAuthSettings()

if oauth_flow:
if not isinstance(oauth_flow, AsyncOAuthFlow):
raise BoltError(error_oauth_flow_invalid_type_async())

self._async_oauth_flow = oauth_flow
installation_store = select_consistent_installation_store(
client_id=self._async_oauth_flow.client_id,
Expand All @@ -182,6 +187,9 @@ def __init__(
if self._async_authorize is None:
self._async_authorize = self._async_oauth_flow.settings.authorize
elif oauth_settings is not None:
if not isinstance(oauth_settings, AsyncOAuthSettings):
raise BoltError(error_oauth_settings_invalid_type_async())

installation_store = select_consistent_installation_store(
client_id=oauth_settings.client_id,
app_store=self._async_installation_store,
Expand Down
8 changes: 8 additions & 0 deletions slack_bolt/logger/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ def error_client_invalid_type_async() -> str:
return "`client` must be a slack_sdk.web.async_client.AsyncWebClient"


def error_oauth_flow_invalid_type_async() -> str:
return "`oauth_flow` must be a slack_bolt.oauth.async_oauth_flow.AsyncOAuthFlow"


def error_oauth_settings_invalid_type_async() -> str:
return "`oauth_settings` must be a slack_bolt.oauth.async_oauth_settings.AsyncOAuthSettings"


def error_auth_test_failure(error_response: SlackResponse) -> str:
return f"`token` is invalid (auth.test result: {error_response})"

Expand Down
5 changes: 5 additions & 0 deletions slack_bolt/oauth/async_oauth_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from typing import Optional, Dict, Callable, Awaitable, Sequence

from slack_bolt.error import BoltError
from slack_bolt.logger.messages import error_oauth_settings_invalid_type_async
from slack_bolt.oauth.async_callback_options import (
AsyncCallbackOptions,
DefaultAsyncCallbackOptions,
Expand Down Expand Up @@ -62,7 +63,11 @@ def __init__(
"""
self._async_client = client
self._logger = logger

if not isinstance(settings, AsyncOAuthSettings):
raise BoltError(error_oauth_settings_invalid_type_async())
self.settings = settings

self.settings.logger = self._logger

self.client_id = self.settings.client_id
Expand Down
10 changes: 7 additions & 3 deletions slack_bolt/oauth/async_oauth_settings.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging
import os
from logging import Logger
from typing import Optional, Sequence
from typing import Optional, Sequence, Union

from slack_sdk.oauth import (
OAuthStateUtils,
Expand Down Expand Up @@ -57,8 +57,8 @@ def __init__(
# OAuth flow parameters/credentials
client_id: Optional[str] = None, # required
client_secret: Optional[str] = None, # required
scopes: Optional[Sequence[str]] = None,
user_scopes: Optional[Sequence[str]] = None,
scopes: Optional[Union[Sequence[str], str]] = None,
user_scopes: Optional[Union[Sequence[str], str]] = None,
redirect_uri: Optional[str] = None,
# Handler configuration
install_path: str = "/slack/install",
Expand Down Expand Up @@ -104,9 +104,13 @@ def __init__(
raise BoltError("Both client_id and client_secret are required")

self.scopes = scopes or os.environ.get("SLACK_SCOPES", "").split(",")
if isinstance(self.scopes, str):
self.scopes = self.scopes.split(",")
self.user_scopes = user_scopes or os.environ.get("SLACK_USER_SCOPES", "").split(
","
)
if isinstance(self.user_scopes, str):
self.user_scopes = self.user_scopes.split(",")
self.redirect_uri = redirect_uri or os.environ.get("SLACK_REDIRECT_URI")
# Handler configuration
self.install_path = install_path or os.environ.get(
Expand Down
10 changes: 7 additions & 3 deletions slack_bolt/oauth/oauth_settings.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging
import os
from logging import Logger
from typing import Optional, Sequence
from typing import Optional, Sequence, Union

from slack_sdk.oauth import (
OAuthStateStore,
Expand Down Expand Up @@ -52,8 +52,8 @@ def __init__(
# OAuth flow parameters/credentials
client_id: Optional[str] = None, # required
client_secret: Optional[str] = None, # required
scopes: Optional[Sequence[str]] = None,
user_scopes: Optional[Sequence[str]] = None,
scopes: Optional[Union[Sequence[str], str]] = None,
user_scopes: Optional[Union[Sequence[str], str]] = None,
redirect_uri: Optional[str] = None,
# Handler configuration
install_path: str = "/slack/install",
Expand Down Expand Up @@ -98,9 +98,13 @@ def __init__(
raise BoltError("Both client_id and client_secret are required")

self.scopes = scopes or os.environ.get("SLACK_SCOPES", "").split(",")
if isinstance(self.scopes, str):
self.scopes = self.scopes.split(",")
self.user_scopes = user_scopes or os.environ.get("SLACK_USER_SCOPES", "").split(
","
)
if isinstance(self.user_scopes, str):
self.user_scopes = self.user_scopes.split(",")
self.redirect_uri = redirect_uri or os.environ.get("SLACK_REDIRECT_URI")
# Handler configuration
self.install_path = install_path or os.environ.get(
Expand Down
13 changes: 12 additions & 1 deletion tests/slack_bolt/oauth/test_oauth_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def test_handle_installation(self):
client_id="111.222",
client_secret="xxx",
scopes=["chat:write", "commands"],
user_scopes=["search:read"],
installation_store=FileInstallationStore(),
state_store=FileOAuthStateStore(expiration_seconds=120),
)
Expand All @@ -54,9 +55,19 @@ def test_handle_installation(self):
resp = oauth_flow.handle_installation(req)
assert resp.status == 200
assert resp.headers.get("content-type") == ["text/html; charset=utf-8"]
assert resp.headers.get("content-length") == ["565"]
assert resp.headers.get("content-length") == ["576"]
assert "https://slack.com/oauth/v2/authorize?state=" in resp.body

def test_scopes_as_str(self):
settings = OAuthSettings(
client_id="111.222",
client_secret="xxx",
scopes="chat:write,commands",
user_scopes="search:read",
)
assert settings.scopes == ["chat:write", "commands"]
assert settings.user_scopes == ["search:read"]

def test_handle_callback(self):
oauth_flow = OAuthFlow(
client=WebClient(base_url=self.mock_api_server_base_url),
Expand Down
37 changes: 37 additions & 0 deletions tests/slack_bolt_async/oauth/test_async_oauth_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@

from slack_bolt import BoltResponse
from slack_bolt.app.async_app import AsyncApp
from slack_bolt.error import BoltError
from slack_bolt.oauth.async_callback_options import (
AsyncFailureArgs,
AsyncSuccessArgs,
AsyncCallbackOptions,
)
from slack_bolt.oauth.async_oauth_flow import AsyncOAuthFlow
from slack_bolt.oauth.async_oauth_settings import AsyncOAuthSettings
from slack_bolt.oauth.oauth_settings import OAuthSettings
from slack_bolt.request.async_request import AsyncBoltRequest
from tests.mock_web_api_server import (
cleanup_mock_web_api_server,
Expand Down Expand Up @@ -46,6 +48,7 @@ async def test_instantiation(self):
client_id="111.222",
client_secret="xxx",
scopes=["chat:write", "commands"],
user_scopes=["search:read"],
installation_store=FileInstallationStore(),
state_store=FileOAuthStateStore(expiration_seconds=120),
)
Expand All @@ -54,6 +57,40 @@ async def test_instantiation(self):
assert oauth_flow.logger is not None
assert oauth_flow.client is not None

@pytest.mark.asyncio
async def test_scopes_as_str(self):
settings = AsyncOAuthSettings(
client_id="111.222",
client_secret="xxx",
scopes="chat:write,commands",
user_scopes="search:read",
)
assert settings.scopes == ["chat:write", "commands"]
assert settings.user_scopes == ["search:read"]

@pytest.mark.asyncio
async def test_instantiation_non_async_settings(self):
with pytest.raises(BoltError):
AsyncOAuthFlow(
settings=OAuthSettings(
client_id="111.222",
client_secret="xxx",
scopes="chat:write,commands",
)
)

@pytest.mark.asyncio
async def test_instantiation_non_async_settings_to_app(self):
with pytest.raises(BoltError):
AsyncApp(
signing_secret="xxx",
oauth_settings=OAuthSettings(
client_id="111.222",
client_secret="xxx",
scopes="chat:write,commands",
),
)

@pytest.mark.asyncio
async def test_handle_installation(self):
oauth_flow = AsyncOAuthFlow(
Expand Down