diff --git a/slack_bolt/oauth/async_oauth_flow.py b/slack_bolt/oauth/async_oauth_flow.py index 233c7d149..dc2d0f094 100644 --- a/slack_bolt/oauth/async_oauth_flow.py +++ b/slack_bolt/oauth/async_oauth_flow.py @@ -162,19 +162,30 @@ def sqlite3( async def handle_installation(self, request: AsyncBoltRequest) -> BoltResponse: state = await self.issue_new_state(request) url = await self.build_authorize_url(state, request) - html = await self.build_install_page_html(url, request) set_cookie_value = self.settings.state_utils.build_set_cookie_for_new_state( state ) - return BoltResponse( - status=200, - body=html, - headers={ - "Content-Type": "text/html; charset=utf-8", - "Content-Length": len(bytes(html, "utf-8")), - "Set-Cookie": [set_cookie_value], - }, - ) + if self.settings.install_page_rendering_enabled: + html = await self.build_install_page_html(url, request) + return BoltResponse( + status=200, + body=html, + headers={ + "Content-Type": "text/html; charset=utf-8", + "Content-Length": len(bytes(html, "utf-8")), + "Set-Cookie": [set_cookie_value], + }, + ) + else: + return BoltResponse( + status=302, + body="", + headers={ + "Content-Type": "text/html; charset=utf-8", + "Location": url, + "Set-Cookie": [set_cookie_value], + }, + ) # ---------------------- # Internal methods for Installation diff --git a/slack_bolt/oauth/async_oauth_settings.py b/slack_bolt/oauth/async_oauth_settings.py index fec1bef80..948bf7547 100644 --- a/slack_bolt/oauth/async_oauth_settings.py +++ b/slack_bolt/oauth/async_oauth_settings.py @@ -32,6 +32,7 @@ class AsyncOAuthSettings: redirect_uri: Optional[str] # Handler configuration install_path: str + install_page_rendering_enabled: bool redirect_uri_path: str callback_options: Optional[CallbackOptions] = None success_url: Optional[str] @@ -63,6 +64,7 @@ def __init__( redirect_uri: Optional[str] = None, # Handler configuration install_path: str = "/slack/install", + install_page_rendering_enabled: bool = True, redirect_uri_path: str = "/slack/oauth_redirect", callback_options: Optional[CallbackOptions] = None, success_url: Optional[str] = None, @@ -86,6 +88,7 @@ def __init__( :param user_scopes: Check the value in Settings > Manage Distribution :param redirect_uri: Check the value in Features > OAuth & Permissions > Redirect URLs :param install_path: The endpoint to start an OAuth flow (Default: /slack/install) + :param install_page_rendering_enabled: Renders a web page for install_path access if True :param redirect_uri_path: The path of Redirect URL (Default: /slack/oauth_redirect) :param callback_options: Give success/failure functions f you want to customize callback functions. :param success_url: Set a complete URL if you want to redirect end-users when an installation completes. @@ -119,6 +122,7 @@ def __init__( self.install_path = install_path or os.environ.get( "SLACK_INSTALL_PATH", "/slack/install" ) + self.install_page_rendering_enabled = install_page_rendering_enabled self.redirect_uri_path = redirect_uri_path or os.environ.get( "SLACK_REDIRECT_URI_PATH", "/slack/oauth_redirect" ) diff --git a/slack_bolt/oauth/oauth_flow.py b/slack_bolt/oauth/oauth_flow.py index 3182c255b..867c90761 100644 --- a/slack_bolt/oauth/oauth_flow.py +++ b/slack_bolt/oauth/oauth_flow.py @@ -157,19 +157,30 @@ def sqlite3( def handle_installation(self, request: BoltRequest) -> BoltResponse: state = self.issue_new_state(request) url = self.build_authorize_url(state, request) - html = self.build_install_page_html(url, request) set_cookie_value = self.settings.state_utils.build_set_cookie_for_new_state( state ) - return BoltResponse( - status=200, - body=html, - headers={ - "Content-Type": "text/html; charset=utf-8", - "Content-Length": len(bytes(html, "utf-8")), - "Set-Cookie": [set_cookie_value], - }, - ) + if self.settings.install_page_rendering_enabled: + html = self.build_install_page_html(url, request) + return BoltResponse( + status=200, + body=html, + headers={ + "Content-Type": "text/html; charset=utf-8", + "Content-Length": len(bytes(html, "utf-8")), + "Set-Cookie": [set_cookie_value], + }, + ) + else: + return BoltResponse( + status=302, + body="", + headers={ + "Content-Type": "text/html; charset=utf-8", + "Location": url, + "Set-Cookie": [set_cookie_value], + }, + ) # ---------------------- # Internal methods for Installation diff --git a/slack_bolt/oauth/oauth_settings.py b/slack_bolt/oauth/oauth_settings.py index 6010e5c26..ebb2fad27 100644 --- a/slack_bolt/oauth/oauth_settings.py +++ b/slack_bolt/oauth/oauth_settings.py @@ -27,6 +27,7 @@ class OAuthSettings: redirect_uri: Optional[str] # Handler configuration install_path: str + install_page_rendering_enabled: bool redirect_uri_path: str callback_options: Optional[CallbackOptions] = None success_url: Optional[str] @@ -58,6 +59,7 @@ def __init__( redirect_uri: Optional[str] = None, # Handler configuration install_path: str = "/slack/install", + install_page_rendering_enabled: bool = True, redirect_uri_path: str = "/slack/oauth_redirect", callback_options: Optional[CallbackOptions] = None, success_url: Optional[str] = None, @@ -81,6 +83,7 @@ def __init__( :param user_scopes: Check the value in Settings > Manage Distribution :param redirect_uri: Check the value in Features > OAuth & Permissions > Redirect URLs :param install_path: The endpoint to start an OAuth flow (Default: /slack/install) + :param install_page_rendering_enabled: Renders a web page for install_path access if True :param redirect_uri_path: The path of Redirect URL (Default: /slack/oauth_redirect) :param callback_options: Give success/failure functions f you want to customize callback functions. :param success_url: Set a complete URL if you want to redirect end-users when an installation completes. @@ -113,6 +116,7 @@ def __init__( self.install_path = install_path or os.environ.get( "SLACK_INSTALL_PATH", "/slack/install" ) + self.install_page_rendering_enabled = install_page_rendering_enabled self.redirect_uri_path = redirect_uri_path or os.environ.get( "SLACK_REDIRECT_URI_PATH", "/slack/oauth_redirect" ) diff --git a/tests/slack_bolt/oauth/test_oauth_flow.py b/tests/slack_bolt/oauth/test_oauth_flow.py index a2797e5e0..cbb119753 100644 --- a/tests/slack_bolt/oauth/test_oauth_flow.py +++ b/tests/slack_bolt/oauth/test_oauth_flow.py @@ -40,7 +40,7 @@ def test_instantiation(self): assert oauth_flow.logger is not None assert oauth_flow.client is not None - def test_handle_installation(self): + def test_handle_installation_default(self): oauth_flow = OAuthFlow( settings=OAuthSettings( client_id="111.222", @@ -58,6 +58,26 @@ def test_handle_installation(self): assert resp.headers.get("content-length") == ["576"] assert "https://slack.com/oauth/v2/authorize?state=" in resp.body + # https://github.com/slackapi/bolt-python/issues/183 + # For direct install URL suppport + def test_handle_installation_no_rendering(self): + oauth_flow = OAuthFlow( + settings=OAuthSettings( + client_id="111.222", + client_secret="xxx", + scopes=["chat:write", "commands"], + user_scopes=["search:read"], + installation_store=FileInstallationStore(), + install_page_rendering_enabled=False, # disabled + state_store=FileOAuthStateStore(expiration_seconds=120), + ) + ) + req = BoltRequest(body="") + resp = oauth_flow.handle_installation(req) + assert resp.status == 302 + location_header = resp.headers.get("location")[0] + assert "https://slack.com/oauth/v2/authorize?state=" in location_header + def test_scopes_as_str(self): settings = OAuthSettings( client_id="111.222", diff --git a/tests/slack_bolt_async/oauth/test_async_oauth_flow.py b/tests/slack_bolt_async/oauth/test_async_oauth_flow.py index af5adbbd2..090429bd3 100644 --- a/tests/slack_bolt_async/oauth/test_async_oauth_flow.py +++ b/tests/slack_bolt_async/oauth/test_async_oauth_flow.py @@ -92,7 +92,7 @@ async def test_instantiation_non_async_settings_to_app(self): ) @pytest.mark.asyncio - async def test_handle_installation(self): + async def test_handle_installation_default(self): oauth_flow = AsyncOAuthFlow( settings=AsyncOAuthSettings( client_id="111.222", @@ -109,6 +109,24 @@ async def test_handle_installation(self): assert resp.headers.get("content-length") == ["565"] assert "https://slack.com/oauth/v2/authorize?state=" in resp.body + @pytest.mark.asyncio + async def test_handle_installation_no_rendering(self): + oauth_flow = AsyncOAuthFlow( + settings=AsyncOAuthSettings( + client_id="111.222", + client_secret="xxx", + scopes=["chat:write", "commands"], + installation_store=FileInstallationStore(), + install_page_rendering_enabled=False, # disabled + state_store=FileOAuthStateStore(expiration_seconds=120), + ) + ) + req = AsyncBoltRequest(body="") + resp = await oauth_flow.handle_installation(req) + assert resp.status == 302 + location_header = resp.headers.get("location")[0] + assert "https://slack.com/oauth/v2/authorize?state=" in location_header + @pytest.mark.asyncio async def test_handle_callback(self): oauth_flow = AsyncOAuthFlow(