Module slack_bolt.oauth.async_oauth_flow
Expand source code
import logging
import os
from logging import Logger
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,
AsyncSuccessArgs,
AsyncFailureArgs,
)
from slack_bolt.oauth.async_oauth_settings import AsyncOAuthSettings
from slack_bolt.oauth.internals import _build_default_install_page_html
from slack_bolt.request.async_request import AsyncBoltRequest
from slack_bolt.response import BoltResponse
from slack_sdk.errors import SlackApiError
from slack_sdk.oauth import OAuthStateUtils
from slack_sdk.oauth.installation_store import Installation
from slack_sdk.oauth.installation_store.sqlite3 import SQLite3InstallationStore
from slack_sdk.oauth.state_store.sqlite3 import SQLite3OAuthStateStore
from slack_sdk.web.async_client import AsyncWebClient
from slack_sdk.web.async_slack_response import AsyncSlackResponse
from slack_bolt.util.async_utils import create_async_web_client
class AsyncOAuthFlow:
settings: AsyncOAuthSettings
client_id: str
redirect_uri: Optional[str]
install_path: str
redirect_uri_path: str
success_handler: Callable[[AsyncSuccessArgs], Awaitable[BoltResponse]]
failure_handler: Callable[[AsyncFailureArgs], Awaitable[BoltResponse]]
@property
def client(self) -> AsyncWebClient:
if self._async_client is None:
self._async_client = create_async_web_client(logger=self.logger)
return self._async_client
@property
def logger(self) -> Logger:
if self._logger is None:
self._logger = logging.getLogger(__name__)
return self._logger
def __init__(
self,
*,
client: Optional[AsyncWebClient] = None,
logger: Optional[Logger] = None,
settings: AsyncOAuthSettings,
):
"""The module to run the Slack app installation flow (OAuth flow).
Args:
client: The `slack_sdk.web.async_client.AsyncWebClient` instance.
logger: The logger.
settings: OAuth settings to configure this module.
"""
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
self.redirect_uri = self.settings.redirect_uri
self.install_path = self.settings.install_path
self.redirect_uri_path = self.settings.redirect_uri_path
self.default_callback_options = DefaultAsyncCallbackOptions(
logger=logger,
state_utils=self.settings.state_utils,
redirect_uri_page_renderer=self.settings.redirect_uri_page_renderer,
)
if settings.callback_options is None:
settings.callback_options = self.default_callback_options
self.success_handler = settings.callback_options.success
self.failure_handler = settings.callback_options.failure
# -----------------------------
# Factory Methods
# -----------------------------
@classmethod
def sqlite3(
cls,
database: str,
# OAuth flow parameters/credentials
authorization_url: Optional[str] = None,
client_id: Optional[str] = None, # required
client_secret: Optional[str] = None, # required
scopes: Optional[Sequence[str]] = None,
user_scopes: Optional[Sequence[str]] = None,
redirect_uri: Optional[str] = None,
# Handler configuration
install_path: Optional[str] = None,
redirect_uri_path: Optional[str] = None,
callback_options: Optional[AsyncCallbackOptions] = None,
success_url: Optional[str] = None,
failure_url: Optional[str] = None,
# Installation Management
# state parameter related configurations
state_cookie_name: str = OAuthStateUtils.default_cookie_name,
state_expiration_seconds: int = OAuthStateUtils.default_expiration_seconds,
installation_store_bot_only: bool = False,
client: Optional[AsyncWebClient] = None,
logger: Optional[Logger] = None,
) -> "AsyncOAuthFlow":
client_id = client_id or os.environ["SLACK_CLIENT_ID"] # required
client_secret = client_secret or os.environ["SLACK_CLIENT_SECRET"] # required
scopes = scopes or os.environ.get("SLACK_SCOPES", "").split(",")
user_scopes = user_scopes or os.environ.get("SLACK_USER_SCOPES", "").split(",")
redirect_uri = redirect_uri or os.environ.get("SLACK_REDIRECT_URI")
return AsyncOAuthFlow(
client=client or AsyncWebClient(),
logger=logger,
settings=AsyncOAuthSettings(
# OAuth flow parameters/credentials
authorization_url=authorization_url,
client_id=client_id,
client_secret=client_secret,
scopes=scopes,
user_scopes=user_scopes,
redirect_uri=redirect_uri,
# Handler configuration
install_path=install_path,
redirect_uri_path=redirect_uri_path,
callback_options=callback_options,
success_url=success_url,
failure_url=failure_url,
# Installation Management
installation_store=SQLite3InstallationStore(
database=database,
client_id=client_id,
logger=logger,
),
installation_store_bot_only=installation_store_bot_only,
# state parameter related configurations
state_store=SQLite3OAuthStateStore(
database=database,
expiration_seconds=state_expiration_seconds,
logger=logger,
),
state_cookie_name=state_cookie_name,
state_expiration_seconds=state_expiration_seconds,
),
)
# -----------------------------
# Installation
# -----------------------------
async def handle_installation(self, request: AsyncBoltRequest) -> BoltResponse:
state = await self.issue_new_state(request)
url = await self.build_authorize_url(state, request)
set_cookie_value = self.settings.state_utils.build_set_cookie_for_new_state(
state
)
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",
"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
async def issue_new_state(self, request: AsyncBoltRequest) -> str:
return await self.settings.state_store.async_issue()
async def build_authorize_url(self, state: str, request: AsyncBoltRequest) -> str:
return self.settings.authorize_url_generator.generate(state)
async def build_install_page_html(self, url: str, request: AsyncBoltRequest) -> str:
return _build_default_install_page_html(url)
# -----------------------------
# Callback
# -----------------------------
async def handle_callback(self, request: AsyncBoltRequest) -> BoltResponse:
# failure due to end-user's cancellation or invalid redirection to slack.com
error = request.query.get("error", [None])[0]
if error is not None:
return await self.failure_handler(
AsyncFailureArgs(
request=request,
reason=error, # type: ignore
suggested_status_code=200,
settings=self.settings,
default=self.default_callback_options,
)
)
# state parameter verification
state: Optional[str] = request.query.get("state", [None])[0]
if not self.settings.state_utils.is_valid_browser(state, request.headers):
return await self.failure_handler(
AsyncFailureArgs(
request=request,
reason="invalid_browser",
suggested_status_code=400,
settings=self.settings,
default=self.default_callback_options,
)
)
valid_state_consumed = await self.settings.state_store.async_consume(state)
if not valid_state_consumed:
return await self.failure_handler(
AsyncFailureArgs(
request=request,
reason="invalid_state",
suggested_status_code=401,
settings=self.settings,
default=self.default_callback_options,
)
)
# run installation
code = request.query.get("code", [None])[0]
if code is None:
return await self.failure_handler(
AsyncFailureArgs(
request=request,
reason="missing_code",
suggested_status_code=401,
settings=self.settings,
default=self.default_callback_options,
)
)
installation = await self.run_installation(code)
if installation is None:
# failed to run installation with the code
return await self.failure_handler(
AsyncFailureArgs(
request=request,
reason="invalid_code",
suggested_status_code=401,
settings=self.settings,
default=self.default_callback_options,
)
)
# persist the installation
try:
await self.store_installation(request, installation)
except BoltError as err:
return await self.failure_handler(
AsyncFailureArgs(
request=request,
reason="storage_error",
error=err,
suggested_status_code=500,
settings=self.settings,
default=self.default_callback_options,
)
)
# display a successful completion page to the end-user
return await self.success_handler(
AsyncSuccessArgs(
request=request,
installation=installation,
settings=self.settings,
default=self.default_callback_options,
)
)
# ----------------------
# Internal methods for Callback
async def run_installation(self, code: str) -> Optional[Installation]:
try:
oauth_response: AsyncSlackResponse = await self.client.oauth_v2_access(
code=code,
client_id=self.settings.client_id,
client_secret=self.settings.client_secret,
redirect_uri=self.settings.redirect_uri, # can be None
)
installed_enterprise: Dict[str, str] = (
oauth_response.get("enterprise") or {}
)
is_enterprise_install: bool = (
oauth_response.get("is_enterprise_install") or False
)
installed_team: Dict[str, str] = oauth_response.get("team") or {}
installer: Dict[str, str] = oauth_response.get("authed_user") or {}
incoming_webhook: Dict[str, str] = (
oauth_response.get("incoming_webhook") or {}
)
bot_token: Optional[str] = oauth_response.get("access_token")
# NOTE: oauth.v2.access doesn't include bot_id in response
bot_id: Optional[str] = None
enterprise_url: Optional[str] = None
if bot_token is not None:
auth_test = await self.client.auth_test(token=bot_token)
bot_id = auth_test["bot_id"]
if is_enterprise_install is True:
enterprise_url = auth_test.get("url")
return Installation(
app_id=oauth_response.get("app_id"),
enterprise_id=installed_enterprise.get("id"),
enterprise_name=installed_enterprise.get("name"),
enterprise_url=enterprise_url,
team_id=installed_team.get("id"),
team_name=installed_team.get("name"),
bot_token=bot_token,
bot_id=bot_id,
bot_user_id=oauth_response.get("bot_user_id"),
bot_scopes=oauth_response.get("scope"), # comma-separated string
user_id=installer.get("id"),
user_token=installer.get("access_token"),
user_scopes=installer.get("scope"), # comma-separated string
incoming_webhook_url=incoming_webhook.get("url"),
incoming_webhook_channel=incoming_webhook.get("channel"),
incoming_webhook_channel_id=incoming_webhook.get("channel_id"),
incoming_webhook_configuration_url=incoming_webhook.get(
"configuration_url"
),
is_enterprise_install=is_enterprise_install,
token_type=oauth_response.get("token_type"),
)
except SlackApiError as e:
message = (
f"Failed to fetch oauth.v2.access result with code: {code} - error: {e}"
)
self.logger.warning(message)
return None
async def store_installation(
self, request: AsyncBoltRequest, installation: Installation
):
# may raise BoltError
await self.settings.installation_store.async_save(installation)
Classes
class AsyncOAuthFlow (*, client: Optional[slack_sdk.web.async_client.AsyncWebClient] = None, logger: Optional[logging.Logger] = None, settings: AsyncOAuthSettings)
-
The module to run the Slack app installation flow (OAuth flow).
Args
client
- The
slack_sdk.web.async_client.AsyncWebClient
instance. logger
- The logger.
settings
- OAuth settings to configure this module.
Expand source code
class AsyncOAuthFlow: settings: AsyncOAuthSettings client_id: str redirect_uri: Optional[str] install_path: str redirect_uri_path: str success_handler: Callable[[AsyncSuccessArgs], Awaitable[BoltResponse]] failure_handler: Callable[[AsyncFailureArgs], Awaitable[BoltResponse]] @property def client(self) -> AsyncWebClient: if self._async_client is None: self._async_client = create_async_web_client(logger=self.logger) return self._async_client @property def logger(self) -> Logger: if self._logger is None: self._logger = logging.getLogger(__name__) return self._logger def __init__( self, *, client: Optional[AsyncWebClient] = None, logger: Optional[Logger] = None, settings: AsyncOAuthSettings, ): """The module to run the Slack app installation flow (OAuth flow). Args: client: The `slack_sdk.web.async_client.AsyncWebClient` instance. logger: The logger. settings: OAuth settings to configure this module. """ 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 self.redirect_uri = self.settings.redirect_uri self.install_path = self.settings.install_path self.redirect_uri_path = self.settings.redirect_uri_path self.default_callback_options = DefaultAsyncCallbackOptions( logger=logger, state_utils=self.settings.state_utils, redirect_uri_page_renderer=self.settings.redirect_uri_page_renderer, ) if settings.callback_options is None: settings.callback_options = self.default_callback_options self.success_handler = settings.callback_options.success self.failure_handler = settings.callback_options.failure # ----------------------------- # Factory Methods # ----------------------------- @classmethod def sqlite3( cls, database: str, # OAuth flow parameters/credentials authorization_url: Optional[str] = None, client_id: Optional[str] = None, # required client_secret: Optional[str] = None, # required scopes: Optional[Sequence[str]] = None, user_scopes: Optional[Sequence[str]] = None, redirect_uri: Optional[str] = None, # Handler configuration install_path: Optional[str] = None, redirect_uri_path: Optional[str] = None, callback_options: Optional[AsyncCallbackOptions] = None, success_url: Optional[str] = None, failure_url: Optional[str] = None, # Installation Management # state parameter related configurations state_cookie_name: str = OAuthStateUtils.default_cookie_name, state_expiration_seconds: int = OAuthStateUtils.default_expiration_seconds, installation_store_bot_only: bool = False, client: Optional[AsyncWebClient] = None, logger: Optional[Logger] = None, ) -> "AsyncOAuthFlow": client_id = client_id or os.environ["SLACK_CLIENT_ID"] # required client_secret = client_secret or os.environ["SLACK_CLIENT_SECRET"] # required scopes = scopes or os.environ.get("SLACK_SCOPES", "").split(",") user_scopes = user_scopes or os.environ.get("SLACK_USER_SCOPES", "").split(",") redirect_uri = redirect_uri or os.environ.get("SLACK_REDIRECT_URI") return AsyncOAuthFlow( client=client or AsyncWebClient(), logger=logger, settings=AsyncOAuthSettings( # OAuth flow parameters/credentials authorization_url=authorization_url, client_id=client_id, client_secret=client_secret, scopes=scopes, user_scopes=user_scopes, redirect_uri=redirect_uri, # Handler configuration install_path=install_path, redirect_uri_path=redirect_uri_path, callback_options=callback_options, success_url=success_url, failure_url=failure_url, # Installation Management installation_store=SQLite3InstallationStore( database=database, client_id=client_id, logger=logger, ), installation_store_bot_only=installation_store_bot_only, # state parameter related configurations state_store=SQLite3OAuthStateStore( database=database, expiration_seconds=state_expiration_seconds, logger=logger, ), state_cookie_name=state_cookie_name, state_expiration_seconds=state_expiration_seconds, ), ) # ----------------------------- # Installation # ----------------------------- async def handle_installation(self, request: AsyncBoltRequest) -> BoltResponse: state = await self.issue_new_state(request) url = await self.build_authorize_url(state, request) set_cookie_value = self.settings.state_utils.build_set_cookie_for_new_state( state ) 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", "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 async def issue_new_state(self, request: AsyncBoltRequest) -> str: return await self.settings.state_store.async_issue() async def build_authorize_url(self, state: str, request: AsyncBoltRequest) -> str: return self.settings.authorize_url_generator.generate(state) async def build_install_page_html(self, url: str, request: AsyncBoltRequest) -> str: return _build_default_install_page_html(url) # ----------------------------- # Callback # ----------------------------- async def handle_callback(self, request: AsyncBoltRequest) -> BoltResponse: # failure due to end-user's cancellation or invalid redirection to slack.com error = request.query.get("error", [None])[0] if error is not None: return await self.failure_handler( AsyncFailureArgs( request=request, reason=error, # type: ignore suggested_status_code=200, settings=self.settings, default=self.default_callback_options, ) ) # state parameter verification state: Optional[str] = request.query.get("state", [None])[0] if not self.settings.state_utils.is_valid_browser(state, request.headers): return await self.failure_handler( AsyncFailureArgs( request=request, reason="invalid_browser", suggested_status_code=400, settings=self.settings, default=self.default_callback_options, ) ) valid_state_consumed = await self.settings.state_store.async_consume(state) if not valid_state_consumed: return await self.failure_handler( AsyncFailureArgs( request=request, reason="invalid_state", suggested_status_code=401, settings=self.settings, default=self.default_callback_options, ) ) # run installation code = request.query.get("code", [None])[0] if code is None: return await self.failure_handler( AsyncFailureArgs( request=request, reason="missing_code", suggested_status_code=401, settings=self.settings, default=self.default_callback_options, ) ) installation = await self.run_installation(code) if installation is None: # failed to run installation with the code return await self.failure_handler( AsyncFailureArgs( request=request, reason="invalid_code", suggested_status_code=401, settings=self.settings, default=self.default_callback_options, ) ) # persist the installation try: await self.store_installation(request, installation) except BoltError as err: return await self.failure_handler( AsyncFailureArgs( request=request, reason="storage_error", error=err, suggested_status_code=500, settings=self.settings, default=self.default_callback_options, ) ) # display a successful completion page to the end-user return await self.success_handler( AsyncSuccessArgs( request=request, installation=installation, settings=self.settings, default=self.default_callback_options, ) ) # ---------------------- # Internal methods for Callback async def run_installation(self, code: str) -> Optional[Installation]: try: oauth_response: AsyncSlackResponse = await self.client.oauth_v2_access( code=code, client_id=self.settings.client_id, client_secret=self.settings.client_secret, redirect_uri=self.settings.redirect_uri, # can be None ) installed_enterprise: Dict[str, str] = ( oauth_response.get("enterprise") or {} ) is_enterprise_install: bool = ( oauth_response.get("is_enterprise_install") or False ) installed_team: Dict[str, str] = oauth_response.get("team") or {} installer: Dict[str, str] = oauth_response.get("authed_user") or {} incoming_webhook: Dict[str, str] = ( oauth_response.get("incoming_webhook") or {} ) bot_token: Optional[str] = oauth_response.get("access_token") # NOTE: oauth.v2.access doesn't include bot_id in response bot_id: Optional[str] = None enterprise_url: Optional[str] = None if bot_token is not None: auth_test = await self.client.auth_test(token=bot_token) bot_id = auth_test["bot_id"] if is_enterprise_install is True: enterprise_url = auth_test.get("url") return Installation( app_id=oauth_response.get("app_id"), enterprise_id=installed_enterprise.get("id"), enterprise_name=installed_enterprise.get("name"), enterprise_url=enterprise_url, team_id=installed_team.get("id"), team_name=installed_team.get("name"), bot_token=bot_token, bot_id=bot_id, bot_user_id=oauth_response.get("bot_user_id"), bot_scopes=oauth_response.get("scope"), # comma-separated string user_id=installer.get("id"), user_token=installer.get("access_token"), user_scopes=installer.get("scope"), # comma-separated string incoming_webhook_url=incoming_webhook.get("url"), incoming_webhook_channel=incoming_webhook.get("channel"), incoming_webhook_channel_id=incoming_webhook.get("channel_id"), incoming_webhook_configuration_url=incoming_webhook.get( "configuration_url" ), is_enterprise_install=is_enterprise_install, token_type=oauth_response.get("token_type"), ) except SlackApiError as e: message = ( f"Failed to fetch oauth.v2.access result with code: {code} - error: {e}" ) self.logger.warning(message) return None async def store_installation( self, request: AsyncBoltRequest, installation: Installation ): # may raise BoltError await self.settings.installation_store.async_save(installation)
Class variables
var client_id : str
var failure_handler : Callable[[AsyncFailureArgs], Awaitable[BoltResponse]]
var install_path : str
var redirect_uri : Optional[str]
var redirect_uri_path : str
var settings : AsyncOAuthSettings
var success_handler : Callable[[AsyncSuccessArgs], Awaitable[BoltResponse]]
Static methods
def sqlite3(database: str, authorization_url: Optional[str] = None, client_id: Optional[str] = None, client_secret: Optional[str] = None, scopes: Optional[Sequence[str]] = None, user_scopes: Optional[Sequence[str]] = None, redirect_uri: Optional[str] = None, install_path: Optional[str] = None, redirect_uri_path: Optional[str] = None, callback_options: Optional[AsyncCallbackOptions] = None, success_url: Optional[str] = None, failure_url: Optional[str] = None, state_cookie_name: str = 'slack-app-oauth-state', state_expiration_seconds: int = 600, installation_store_bot_only: bool = False, client: Optional[slack_sdk.web.async_client.AsyncWebClient] = None, logger: Optional[logging.Logger] = None) ‑> AsyncOAuthFlow
-
Expand source code
@classmethod def sqlite3( cls, database: str, # OAuth flow parameters/credentials authorization_url: Optional[str] = None, client_id: Optional[str] = None, # required client_secret: Optional[str] = None, # required scopes: Optional[Sequence[str]] = None, user_scopes: Optional[Sequence[str]] = None, redirect_uri: Optional[str] = None, # Handler configuration install_path: Optional[str] = None, redirect_uri_path: Optional[str] = None, callback_options: Optional[AsyncCallbackOptions] = None, success_url: Optional[str] = None, failure_url: Optional[str] = None, # Installation Management # state parameter related configurations state_cookie_name: str = OAuthStateUtils.default_cookie_name, state_expiration_seconds: int = OAuthStateUtils.default_expiration_seconds, installation_store_bot_only: bool = False, client: Optional[AsyncWebClient] = None, logger: Optional[Logger] = None, ) -> "AsyncOAuthFlow": client_id = client_id or os.environ["SLACK_CLIENT_ID"] # required client_secret = client_secret or os.environ["SLACK_CLIENT_SECRET"] # required scopes = scopes or os.environ.get("SLACK_SCOPES", "").split(",") user_scopes = user_scopes or os.environ.get("SLACK_USER_SCOPES", "").split(",") redirect_uri = redirect_uri or os.environ.get("SLACK_REDIRECT_URI") return AsyncOAuthFlow( client=client or AsyncWebClient(), logger=logger, settings=AsyncOAuthSettings( # OAuth flow parameters/credentials authorization_url=authorization_url, client_id=client_id, client_secret=client_secret, scopes=scopes, user_scopes=user_scopes, redirect_uri=redirect_uri, # Handler configuration install_path=install_path, redirect_uri_path=redirect_uri_path, callback_options=callback_options, success_url=success_url, failure_url=failure_url, # Installation Management installation_store=SQLite3InstallationStore( database=database, client_id=client_id, logger=logger, ), installation_store_bot_only=installation_store_bot_only, # state parameter related configurations state_store=SQLite3OAuthStateStore( database=database, expiration_seconds=state_expiration_seconds, logger=logger, ), state_cookie_name=state_cookie_name, state_expiration_seconds=state_expiration_seconds, ), )
Instance variables
var client : slack_sdk.web.async_client.AsyncWebClient
-
Expand source code
@property def client(self) -> AsyncWebClient: if self._async_client is None: self._async_client = create_async_web_client(logger=self.logger) return self._async_client
var logger : logging.Logger
-
Expand source code
@property def logger(self) -> Logger: if self._logger is None: self._logger = logging.getLogger(__name__) return self._logger
Methods
-
Expand source code
async def build_authorize_url(self, state: str, request: AsyncBoltRequest) -> str: return self.settings.authorize_url_generator.generate(state)
async def build_install_page_html(self, url: str, request: AsyncBoltRequest) ‑> str
-
Expand source code
async def build_install_page_html(self, url: str, request: AsyncBoltRequest) -> str: return _build_default_install_page_html(url)
async def handle_callback(self, request: AsyncBoltRequest) ‑> BoltResponse
-
Expand source code
async def handle_callback(self, request: AsyncBoltRequest) -> BoltResponse: # failure due to end-user's cancellation or invalid redirection to slack.com error = request.query.get("error", [None])[0] if error is not None: return await self.failure_handler( AsyncFailureArgs( request=request, reason=error, # type: ignore suggested_status_code=200, settings=self.settings, default=self.default_callback_options, ) ) # state parameter verification state: Optional[str] = request.query.get("state", [None])[0] if not self.settings.state_utils.is_valid_browser(state, request.headers): return await self.failure_handler( AsyncFailureArgs( request=request, reason="invalid_browser", suggested_status_code=400, settings=self.settings, default=self.default_callback_options, ) ) valid_state_consumed = await self.settings.state_store.async_consume(state) if not valid_state_consumed: return await self.failure_handler( AsyncFailureArgs( request=request, reason="invalid_state", suggested_status_code=401, settings=self.settings, default=self.default_callback_options, ) ) # run installation code = request.query.get("code", [None])[0] if code is None: return await self.failure_handler( AsyncFailureArgs( request=request, reason="missing_code", suggested_status_code=401, settings=self.settings, default=self.default_callback_options, ) ) installation = await self.run_installation(code) if installation is None: # failed to run installation with the code return await self.failure_handler( AsyncFailureArgs( request=request, reason="invalid_code", suggested_status_code=401, settings=self.settings, default=self.default_callback_options, ) ) # persist the installation try: await self.store_installation(request, installation) except BoltError as err: return await self.failure_handler( AsyncFailureArgs( request=request, reason="storage_error", error=err, suggested_status_code=500, settings=self.settings, default=self.default_callback_options, ) ) # display a successful completion page to the end-user return await self.success_handler( AsyncSuccessArgs( request=request, installation=installation, settings=self.settings, default=self.default_callback_options, ) )
async def handle_installation(self, request: AsyncBoltRequest) ‑> BoltResponse
-
Expand source code
async def handle_installation(self, request: AsyncBoltRequest) -> BoltResponse: state = await self.issue_new_state(request) url = await self.build_authorize_url(state, request) set_cookie_value = self.settings.state_utils.build_set_cookie_for_new_state( state ) 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", "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], }, )
async def issue_new_state(self, request: AsyncBoltRequest) ‑> str
-
Expand source code
async def issue_new_state(self, request: AsyncBoltRequest) -> str: return await self.settings.state_store.async_issue()
async def run_installation(self, code: str) ‑> Optional[slack_sdk.oauth.installation_store.models.installation.Installation]
-
Expand source code
async def run_installation(self, code: str) -> Optional[Installation]: try: oauth_response: AsyncSlackResponse = await self.client.oauth_v2_access( code=code, client_id=self.settings.client_id, client_secret=self.settings.client_secret, redirect_uri=self.settings.redirect_uri, # can be None ) installed_enterprise: Dict[str, str] = ( oauth_response.get("enterprise") or {} ) is_enterprise_install: bool = ( oauth_response.get("is_enterprise_install") or False ) installed_team: Dict[str, str] = oauth_response.get("team") or {} installer: Dict[str, str] = oauth_response.get("authed_user") or {} incoming_webhook: Dict[str, str] = ( oauth_response.get("incoming_webhook") or {} ) bot_token: Optional[str] = oauth_response.get("access_token") # NOTE: oauth.v2.access doesn't include bot_id in response bot_id: Optional[str] = None enterprise_url: Optional[str] = None if bot_token is not None: auth_test = await self.client.auth_test(token=bot_token) bot_id = auth_test["bot_id"] if is_enterprise_install is True: enterprise_url = auth_test.get("url") return Installation( app_id=oauth_response.get("app_id"), enterprise_id=installed_enterprise.get("id"), enterprise_name=installed_enterprise.get("name"), enterprise_url=enterprise_url, team_id=installed_team.get("id"), team_name=installed_team.get("name"), bot_token=bot_token, bot_id=bot_id, bot_user_id=oauth_response.get("bot_user_id"), bot_scopes=oauth_response.get("scope"), # comma-separated string user_id=installer.get("id"), user_token=installer.get("access_token"), user_scopes=installer.get("scope"), # comma-separated string incoming_webhook_url=incoming_webhook.get("url"), incoming_webhook_channel=incoming_webhook.get("channel"), incoming_webhook_channel_id=incoming_webhook.get("channel_id"), incoming_webhook_configuration_url=incoming_webhook.get( "configuration_url" ), is_enterprise_install=is_enterprise_install, token_type=oauth_response.get("token_type"), ) except SlackApiError as e: message = ( f"Failed to fetch oauth.v2.access result with code: {code} - error: {e}" ) self.logger.warning(message) return None
async def store_installation(self, request: AsyncBoltRequest, installation: slack_sdk.oauth.installation_store.models.installation.Installation)
-
Expand source code
async def store_installation( self, request: AsyncBoltRequest, installation: Installation ): # may raise BoltError await self.settings.installation_store.async_save(installation)