def _init_middleware_list(self): if self._init_middleware_list_done: return self._middleware_list.append( SslCheck(verification_token=self._verification_token) ) self._middleware_list.append(RequestVerification(self._signing_secret)) if self._oauth_flow is None: if self._token is not None: try: auth_test_result = self._client.auth_test(token=self._token) self._middleware_list.append( SingleTeamAuthorization(auth_test_result=auth_test_result) ) except SlackApiError as err: raise BoltError(error_auth_test_failure(err.response)) elif self._authorize is not None: self._middleware_list.append( MultiTeamsAuthorization(authorize=self._authorize) ) else: raise BoltError(error_token_required()) else: self._middleware_list.append( MultiTeamsAuthorization(authorize=self._authorize) ) self._middleware_list.append(IgnoringSelfEvents()) self._middleware_list.append(UrlVerification()) self._init_middleware_list_done = True
def step( self, callback_id: Union[str, Pattern, AsyncWorkflowStep], edit: Optional[ Union[ Callable[..., Optional[BoltResponse]], AsyncListener, Sequence[Callable] ] ] = None, save: Optional[ Union[ Callable[..., Optional[BoltResponse]], AsyncListener, Sequence[Callable] ] ] = None, execute: Optional[ Union[ Callable[..., Optional[BoltResponse]], AsyncListener, Sequence[Callable] ] ] = None, ): """Registers a new Workflow Step listener""" step = callback_id if isinstance(callback_id, (str, Pattern)): step = AsyncWorkflowStep( callback_id=callback_id, edit=edit, save=save, execute=execute, ) elif not isinstance(step, AsyncWorkflowStep): raise BoltError("Invalid step object") self.use(AsyncWorkflowStepMiddleware(step, self._async_listener_runner))
def _init_async_middleware_list(self): if self._init_middleware_list_done: return self._async_middleware_list.append( AsyncSslCheck(verification_token=self._verification_token) ) self._async_middleware_list.append( AsyncRequestVerification(self._signing_secret) ) if self._async_oauth_flow is None: if self._token: self._async_middleware_list.append(AsyncSingleTeamAuthorization()) elif self._async_authorize is not None: self._async_middleware_list.append( AsyncMultiTeamsAuthorization(authorize=self._async_authorize) ) else: raise BoltError(error_token_required()) else: self._async_middleware_list.append( AsyncMultiTeamsAuthorization(authorize=self._async_authorize) ) self._async_middleware_list.append(AsyncIgnoringSelfEvents()) self._async_middleware_list.append(AsyncUrlVerification()) self._init_middleware_list_done = True
def step( self, callback_id: Union[str, Pattern, AsyncWorkflowStep, AsyncWorkflowStepBuilder], edit: Optional[Union[Callable[..., Optional[BoltResponse]], AsyncListener, Sequence[Callable]]] = None, save: Optional[Union[Callable[..., Optional[BoltResponse]], AsyncListener, Sequence[Callable]]] = None, execute: Optional[Union[Callable[..., Optional[BoltResponse]], AsyncListener, Sequence[Callable]]] = None, ): """Registers a new Workflow Step listener Unlike others, this method doesn't behave as a decorator. If you want to register a workflow step by a decorator, use AsyncWorkflowStepBuilder's methods. """ step = callback_id if isinstance(callback_id, (str, Pattern)): step = AsyncWorkflowStep( callback_id=callback_id, edit=edit, save=save, execute=execute, ) elif isinstance(step, AsyncWorkflowStepBuilder): step = step.build() elif not isinstance(step, AsyncWorkflowStep): raise BoltError(f"Invalid step object ({type(step)})") self.use(AsyncWorkflowStepMiddleware(step, self._async_listener_runner))
def event( constraints: Union[str, Pattern, Dict[str, str]], asyncio: bool = False, ) -> Union[ListenerMatcher, "AsyncListenerMatcher"]: if isinstance(constraints, (str, Pattern)): event_type: Union[str, Pattern] = constraints def func(body: Dict[str, Any]) -> bool: return is_event(body) and _matches(event_type, body["event"]["type"]) return build_listener_matcher(func, asyncio) elif "type" in constraints: def func(body: Dict[str, Any]) -> bool: if is_event(body): event = body["event"] if not _matches(constraints["type"], event["type"]): return False if "subtype" in constraints: expected_subtype = constraints["subtype"] if expected_subtype is None: # "subtype" in constraints is intentionally None for this pattern return "subtype" not in event else: return "subtype" in event and _matches( expected_subtype, event["subtype"] ) return True return False return build_listener_matcher(func, asyncio) raise BoltError( f"event ({constraints}: {type(constraints)}) must be any of str, Pattern, and dict" )
def event( constraints: Union[str, Pattern, Dict[str, Union[str, Sequence[Optional[Union[str, Pattern]]]]]], asyncio: bool = False, ) -> Union[ListenerMatcher, "AsyncListenerMatcher"]: if isinstance(constraints, (str, Pattern)): event_type: Union[str, Pattern] = constraints _verify_message_event_type(event_type) def func(body: Dict[str, Any]) -> bool: return is_event(body) and _matches(event_type, body["event"]["type"]) return build_listener_matcher(func, asyncio) elif "type" in constraints: _verify_message_event_type(constraints["type"]) def func(body: Dict[str, Any]) -> bool: if is_event(body): event = body["event"] if not _matches(constraints["type"], event["type"]): return False if "subtype" in constraints: expected_subtype: Union[str, Sequence[Optional[Union[ str, Pattern]]]] = constraints["subtype"] if expected_subtype is None: # "subtype" in constraints is intentionally None for this pattern return "subtype" not in event elif isinstance(expected_subtype, (str, Pattern)): return "subtype" in event and _matches( expected_subtype, event["subtype"]) elif isinstance(expected_subtype, Sequence): subtypes: Sequence[Optional[Union[ str, Pattern]]] = expected_subtype for expected in subtypes: actual: Optional[str] = event.get("subtype") if expected is None: if actual is None: return True elif actual is not None and _matches( expected, actual): return True return False else: return "subtype" in event and _matches( expected_subtype, event["subtype"]) return True return False return build_listener_matcher(func, asyncio) raise BoltError( f"event ({constraints}: {type(constraints)}) must be any of str, Pattern, and dict" )
def build(self) -> "WorkflowStep": """Constructs a WorkflowStep object. This method may raise an exception if the builder doesn't have enough configurations to build the object. :return: WorkflowStep object """ if self._edit is None: raise BoltError(f"edit listener is not registered") if self._save is None: raise BoltError(f"save listener is not registered") if self._execute is None: raise BoltError(f"execute listener is not registered") return WorkflowStep( callback_id=self.callback_id, edit=self._edit, save=self._save, execute=self._execute, app_name=self.app_name, )
def __init__( self, *, body: Union[str, dict], query: Optional[Union[str, Dict[str, str], Dict[str, Sequence[str]]]] = None, headers: Optional[Dict[str, Union[str, Sequence[str]]]] = None, context: Optional[Dict[str, str]] = None, mode: str = "http", # either "http" or "socket_mode" ): """Request to a Bolt app. Args: body: The raw request body (only plain text is supported for "http" mode) query: The query string data in any data format. headers: The request headers. context: The context in this request. mode: The mode used for this request. (either "http" or "socket_mode") """ if mode == "http": # HTTP Mode if body is not None and not isinstance(body, str): raise BoltError(error_message_raw_body_required_in_http_mode()) self.raw_body = body if body is not None else "" else: # Socket Mode if body is not None and isinstance(body, str): self.raw_body = body else: # We don't convert the dict value to str # as doing so does not guarantee to keep the original structure/format. self.raw_body = "" self.query = parse_query(query) self.headers = build_normalized_headers(headers) self.content_type = extract_content_type(self.headers) if isinstance(body, str): self.body = parse_body(self.raw_body, self.content_type) elif isinstance(body, dict): self.body = body else: self.body = {} self.context = build_async_context( AsyncBoltContext(context if context else {}), self.body) self.lazy_only = bool( self.headers.get("x-slack-bolt-lazy-only", [False])[0]) self.lazy_function_name = self.headers.get( "x-slack-bolt-lazy-function-name", [None])[0] self.mode = mode
def action( constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]], asyncio: bool = False, ) -> Union[ListenerMatcher, "AsyncListenerMatcher"]: if isinstance(constraints, (str, Pattern)): def func(body: Dict[str, Any]) -> bool: return (_block_action(constraints, body) or _attachment_action(constraints, body) or _dialog_submission(constraints, body) or _dialog_cancellation(constraints, body) or _workflow_step_edit(constraints, body)) return build_listener_matcher(func, asyncio) elif "type" in constraints: action_type = constraints["type"] if action_type == "block_actions": return block_action(constraints, asyncio) if action_type == "interactive_message": return attachment_action(constraints["callback_id"], asyncio) if action_type == "dialog_submission": return dialog_submission(constraints["callback_id"], asyncio) if action_type == "dialog_cancellation": return dialog_cancellation(constraints["callback_id"], asyncio) # Still in beta # https://api.slack.com/workflows/steps if action_type == "workflow_step_edit": return workflow_step_edit(constraints["callback_id"], asyncio) raise BoltError(f"type: {action_type} is unsupported") elif "action_id" in constraints: # The default value is "block_actions" return block_action(constraints, asyncio) raise BoltError( f"action ({constraints}: {type(constraints)}) must be any of str, Pattern, and dict" )
def build_listener( cls, callback_id: Union[str, Pattern], app_name: str, listener_or_functions: Union[Listener, Callable, List[Callable]], name: str, matchers: Optional[List[ListenerMatcher]] = None, middleware: Optional[List[Middleware]] = None, ) -> Listener: if listener_or_functions is None: raise BoltError( f"{name} listener is required (callback_id: {callback_id})") if isinstance(listener_or_functions, Callable): listener_or_functions = [listener_or_functions] if isinstance(listener_or_functions, Listener): return listener_or_functions elif isinstance(listener_or_functions, list): matchers = matchers if matchers else [] matchers.insert(0, cls._build_primary_matcher(name, callback_id)) middleware = middleware if middleware else [] middleware.insert(0, cls._build_single_middleware(name, callback_id)) functions = listener_or_functions ack_function = functions.pop(0) return CustomListener( app_name=app_name, matchers=matchers, middleware=middleware, ack_function=ack_function, lazy_functions=functions, auto_acknowledgement=name == "execute", ) else: raise BoltError( f"Invalid {name} listener: {type(listener_or_functions)} detected (callback_id: {callback_id})" )
def __init__( self, *, body: Union[str, dict], query: Optional[Union[str, Dict[str, str], Dict[str, Sequence[str]]]] = None, headers: Optional[Dict[str, Union[str, Sequence[str]]]] = None, context: Optional[Dict[str, str]] = None, mode: str = "http", # either "http" or "socket_mode" ): """Request to a Bolt app. Args: body: The raw request body (only plain text is supported for "http" mode) query: The query string data in any data format. headers: The request headers. context: The context in this request. mode: The mode used for this request. (either "http" or "socket_mode") """ if mode == "http" and not isinstance(body, str): raise BoltError(error_message_raw_body_required_in_http_mode()) self.raw_body = body if mode == "http" else "" self.query = parse_query(query) self.headers = build_normalized_headers(headers) self.content_type = extract_content_type(self.headers) if isinstance(body, str): self.body = parse_body(self.raw_body, self.content_type) elif isinstance(body, dict): self.body = body else: raise BoltError(error_message_unknown_request_body_type()) self.context = build_context(BoltContext(context if context else {}), self.body) self.lazy_only = self.headers.get("x-slack-bolt-lazy-only", [False])[0] self.lazy_function_name = self.headers.get( "x-slack-bolt-lazy-function-name", [None])[0] self.mode = mode
def _matches(str_or_pattern: Union[str, Pattern], input: Optional[str]) -> bool: if str_or_pattern is None or input is None: return False if isinstance(str_or_pattern, str): exact_match_str: str = str_or_pattern return input == exact_match_str elif isinstance(str_or_pattern, Pattern): pattern: Pattern = str_or_pattern return pattern.search(input) is not None else: raise BoltError( f"{str_or_pattern} ({type(str_or_pattern)}) must be either str or Pattern" )
def _register_listener( self, functions: Sequence[Callable[..., Awaitable[Optional[BoltResponse]]]], primary_matcher: AsyncListenerMatcher, matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]], middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]], auto_acknowledgement: bool = False, ) -> Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]: value_to_return = None if not isinstance(functions, list): functions = list(functions) if len(functions) == 1: # In the case where the function is registered using decorator, # the registration should return the original function. value_to_return = functions[0] for func in functions: if not inspect.iscoroutinefunction(func): name = get_name_for_callable(func) raise BoltError(error_listener_function_must_be_coro_func(name)) listener_matchers = [ AsyncCustomListenerMatcher(app_name=self.name, func=f) for f in (matchers or []) ] listener_matchers.insert(0, primary_matcher) listener_middleware = [] for m in middleware or []: if isinstance(m, AsyncMiddleware): listener_middleware.append(m) elif isinstance(m, Callable) and inspect.iscoroutinefunction(m): listener_middleware.append( AsyncCustomMiddleware(app_name=self.name, func=m) ) else: raise ValueError(error_unexpected_listener_middleware(type(m))) self._async_listeners.append( AsyncCustomListener( app_name=self.name, ack_function=functions.pop(0), lazy_functions=functions, matchers=listener_matchers, middleware=listener_middleware, auto_acknowledgement=auto_acknowledgement, ) ) return value_to_return
def view( constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]], asyncio: bool = False, ) -> Union[ListenerMatcher, "AsyncListenerMatcher"]: if isinstance(constraints, (str, Pattern)): return view_submission(constraints, asyncio) elif "type" in constraints: if constraints["type"] == "view_submission": return view_submission(constraints["callback_id"], asyncio) if constraints["type"] == "view_closed": return view_closed(constraints["callback_id"], asyncio) raise BoltError( f"view ({constraints}: {type(constraints)}) must be any of str, Pattern, and dict" )
def middleware(self, *args) -> Optional[Callable]: """Registers a new middleware to this Bolt app. :param args: a list of middleware. Passing a single middleware is supported. :return: None """ if len(args) > 0: middleware_or_callable = args[0] if isinstance(middleware_or_callable, AsyncMiddleware): self._async_middleware_list.append(middleware_or_callable) elif isinstance(middleware_or_callable, Callable): self._async_middleware_list.append( AsyncCustomMiddleware(app_name=self.name, func=middleware_or_callable)) return middleware_or_callable else: raise BoltError( f"Unexpected type for a middleware ({type(middleware_or_callable)})" ) return None
def options( constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]], asyncio: bool = False, ) -> Union[ListenerMatcher, "AsyncListenerMatcher"]: if isinstance(constraints, (str, Pattern)): def func(body: Dict[str, Any]) -> bool: return _block_suggestion(constraints, body) or _dialog_suggestion( constraints, body) return build_listener_matcher(func, asyncio) if "action_id" in constraints: return block_suggestion(constraints["action_id"], asyncio) if "callback_id" in constraints: return dialog_suggestion(constraints["callback_id"], asyncio) else: raise BoltError( f"options ({constraints}: {type(constraints)}) must be any of str, Pattern, and dict" )
def shortcut( constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]], asyncio: bool = False, ) -> Union[ListenerMatcher, "AsyncListenerMatcher"]: if isinstance(constraints, (str, Pattern)): callback_id: Union[str, Pattern] = constraints def func(body: Dict[str, Any]) -> bool: return is_shortcut(body) and _matches(callback_id, body["callback_id"]) return build_listener_matcher(func, asyncio) elif "type" in constraints and "callback_id" in constraints: if constraints["type"] == "shortcut": return global_shortcut(constraints["callback_id"], asyncio) if constraints["type"] == "message_action": return message_shortcut(constraints["callback_id"], asyncio) raise BoltError( f"shortcut ({constraints}: {type(constraints)}) must be any of str, Pattern, and dict" )
def _register_listener( self, func: Callable[..., Awaitable[BoltResponse]], primary_matcher: AsyncListenerMatcher, matchers: Optional[List[Callable[..., Awaitable[bool]]]], middleware: Optional[List[Union[Callable, AsyncMiddleware]]], auto_acknowledgement: bool = False, ) -> None: if not inspect.iscoroutinefunction(func): name = func.__name__ raise BoltError( f"The listener function ({name}) is not a coroutine function.") listener_matchers = [ AsyncCustomListenerMatcher(app_name=self.name, func=f) for f in (matchers or []) ] listener_matchers.insert(0, primary_matcher) listener_middleware = [] for m in middleware or []: if isinstance(m, AsyncMiddleware): listener_middleware.append(m) elif isinstance(m, Callable) and inspect.iscoroutinefunction(m): listener_middleware.append( AsyncCustomMiddleware(app_name=self.name, func=m)) else: raise ValueError( f"async function is required for AsyncApp's listener middleware: {type(m)}" ) self._async_listeners.append( AsyncCustomListener( app_name=self.name, func=func, matchers=listener_matchers, middleware=listener_middleware, auto_acknowledgement=auto_acknowledgement, ))
def _init_async_middleware_list(self): if self._init_middleware_list_done: return self._async_middleware_list.append( AsyncSslCheck(verification_token=self._verification_token)) self._async_middleware_list.append( AsyncRequestVerification(self._signing_secret)) if self._async_oauth_flow is None: if self._token: self._async_middleware_list.append( AsyncSingleTeamAuthorization()) else: raise BoltError( "AsyncOAuthFlow not found, so could not initialize the Bolt app." ) else: self._async_middleware_list.append( AsyncMultiTeamsAuthorization(self._async_installation_store)) self._async_middleware_list.append(AsyncIgnoringSelfEvents()) self._async_middleware_list.append(AsyncUrlVerification()) self._init_middleware_list_done = True
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
def _init_middleware_list(self): if self._init_middleware_list_done: return self._middleware_list.append( SslCheck(verification_token=self._verification_token)) self._middleware_list.append(RequestVerification(self._signing_secret)) if self._oauth_flow is None: if self._token: self._middleware_list.append(SingleTeamAuthorization()) else: raise BoltError( "OAuthFlow not found, so could not initialize the Bolt app." ) else: self._middleware_list.append( MultiTeamsAuthorization( installation_store=self._installation_store, verification_enabled=self._authorization_test_enabled, )) self._middleware_list.append(IgnoringSelfEvents()) self._middleware_list.append(UrlVerification()) self._init_middleware_list_done = True
def __init__(self, app: App): # type: ignore self.app = app listener_runner = self.app.listener_runner # This runner closes all thread-local connections in the thread when an execution completes self.app.listener_runner.lazy_listener_runner = DjangoThreadLazyListenerRunner( logger=listener_runner.logger, executor=listener_runner.listener_executor, ) if not isinstance(listener_runner, ThreadListenerRunner): raise BoltError( "Custom listener_runners are not compatible with this Django adapter." ) if app.process_before_response is True: # As long as the app access Django models in the same thread, # Django cleans the connections up for you. self.app.logger.debug("App.process_before_response is set to True") return current_completion_handler = listener_runner.listener_completion_handler if current_completion_handler is not None and not isinstance( current_completion_handler, DefaultListenerCompletionHandler): message = """As you've already set app.listener_runner.listener_completion_handler to your own one, Bolt skipped to set it to slack_sdk.adapter.django.DjangoListenerCompletionHandler. We strongly recommend having the following lines of code in your listener_completion_handler: from django.db import connections connections.close_all() """ self.app.logger.warning(message) return # for proper management of thread-local Django DB connections self.app.listener_runner.listener_completion_handler = ( DjangoListenerCompletionHandler()) self.app.logger.debug( "DjangoListenerCompletionHandler has been enabled")
def __init__( self, *, # OAuth flow parameters/credentials client_id: Optional[str] = None, # required client_secret: Optional[str] = None, # required 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", install_page_rendering_enabled: bool = True, redirect_uri_path: str = "/slack/oauth_redirect", callback_options: Optional[CallbackOptions] = None, success_url: Optional[str] = None, failure_url: Optional[str] = None, authorization_url: Optional[str] = None, # Installation Management installation_store: Optional[InstallationStore] = None, installation_store_bot_only: bool = False, # state parameter related configurations state_store: Optional[OAuthStateStore] = None, state_cookie_name: str = OAuthStateUtils.default_cookie_name, state_expiration_seconds: int = OAuthStateUtils. default_expiration_seconds, # Others logger: Logger = logging.getLogger(__name__), ): """The settings for Slack App installation (OAuth flow). Args: client_id: Check the value in Settings > Basic Information > App Credentials client_secret: Check the value in Settings > Basic Information > App Credentials scopes: Check the value in Settings > Manage Distribution user_scopes: Check the value in Settings > Manage Distribution redirect_uri: Check the value in Features > OAuth & Permissions > Redirect URLs install_path: The endpoint to start an OAuth flow (Default: `/slack/install`) install_page_rendering_enabled: Renders a web page for install_path access if True redirect_uri_path: The path of Redirect URL (Default: `/slack/oauth_redirect`) callback_options: Give success/failure functions f you want to customize callback functions. success_url: Set a complete URL if you want to redirect end-users when an installation completes. failure_url: Set a complete URL if you want to redirect end-users when an installation fails. authorization_url: Set a URL if you want to customize the URL `https://slack.com/oauth/v2/authorize` installation_store: Specify the instance of `InstallationStore` (Default: `FileInstallationStore`) installation_store_bot_only: Use `InstallationStore#find_bot()` if True (Default: False) state_store: Specify the instance of `InstallationStore` (Default: `FileOAuthStateStore`) state_cookie_name: The cookie name that is set for installers' browser. (Default: "slack-app-oauth-state") state_expiration_seconds: The seconds that the state value is alive (Default: 600 seconds) logger: The logger that will be used internally """ self.client_id = client_id or os.environ.get("SLACK_CLIENT_ID") self.client_secret = client_secret or os.environ.get( "SLACK_CLIENT_SECRET", None) if self.client_id is None or self.client_secret is None: 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( "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") self.callback_options = callback_options self.success_url = success_url self.failure_url = failure_url self.authorization_url = (authorization_url or "https://slack.com/oauth/v2/authorize") # Installation Management self.installation_store = ( installation_store or get_or_create_default_installation_store(client_id)) self.installation_store_bot_only = installation_store_bot_only self.authorize = InstallationStoreAuthorize( logger=logger, installation_store=self.installation_store, bot_only=self.installation_store_bot_only, ) # state parameter related configurations self.state_store = state_store or FileOAuthStateStore( expiration_seconds=state_expiration_seconds, client_id=client_id, ) self.state_cookie_name = state_cookie_name self.state_expiration_seconds = state_expiration_seconds self.state_utils = OAuthStateUtils( cookie_name=self.state_cookie_name, expiration_seconds=self.state_expiration_seconds, ) self.authorize_url_generator = AuthorizeUrlGenerator( client_id=self.client_id, redirect_uri=self.redirect_uri, scopes=self.scopes, user_scopes=self.user_scopes, authorization_url=self.authorization_url, ) self.redirect_uri_page_renderer = RedirectUriPageRenderer( install_path=self.install_path, redirect_uri_path=self.redirect_uri_path, success_url=self.success_url, failure_url=self.failure_url, )
def __init__( self, *, # Used in logger name: Optional[str] = None, # Set True when you run this app on a FaaS platform process_before_response: bool = False, # Basic Information > Credentials > Signing Secret signing_secret: Optional[str] = None, # for single-workspace apps token: Optional[str] = None, client: Optional[AsyncWebClient] = None, # for multi-workspace apps installation_store: Optional[AsyncInstallationStore] = None, oauth_state_store: Optional[AsyncOAuthStateStore] = None, oauth_state_cookie_name: str = OAuthStateUtils.default_cookie_name, oauth_state_expiration_seconds: int = OAuthStateUtils. default_expiration_seconds, # for the OAuth flow oauth_flow: Optional[AsyncOAuthFlow] = None, client_id: Optional[str] = None, client_secret: Optional[str] = None, scopes: Optional[List[str]] = None, user_scopes: Optional[List[str]] = None, redirect_uri: Optional[str] = None, oauth_install_path: Optional[str] = None, oauth_redirect_uri_path: Optional[str] = None, oauth_success_url: Optional[str] = None, oauth_failure_url: Optional[str] = None, # No need to set (the value is used only in response to ssl_check requests) verification_token: Optional[str] = None, ): signing_secret = signing_secret or os.environ.get( "SLACK_SIGNING_SECRET", None) token = token or os.environ.get("SLACK_BOT_TOKEN", None) if signing_secret is None or signing_secret == "": raise BoltError( "Signing secret not found, so could not initialize the Bolt app." ) self._name: str = name or inspect.stack()[1].filename.split( os.path.sep)[-1] self._signing_secret: str = signing_secret client_id = client_id or os.environ.get("SLACK_CLIENT_ID", None) client_secret = client_secret or os.environ.get( "SLACK_CLIENT_SECRET", None) 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", None) oauth_install_path = oauth_install_path or os.environ.get( "SLACK_INSTALL_PATH", "/slack/install") oauth_redirect_uri_path = oauth_redirect_uri_path or os.environ.get( "SLACK_REDIRECT_URI_PATH", "/slack/oauth_redirect") self._verification_token: Optional[ str] = verification_token or os.environ.get( "SLACK_VERIFICATION_TOKEN", None) self._framework_logger = get_bolt_logger(AsyncApp) self._token: Optional[str] = token if client is not None: self._async_client = client self._token = client.token if token is not None: self._framework_logger.warning( "As you gave client as well, the bot token will be unused." ) else: # NOTE: the token here can be None self._async_client = create_async_web_client(token) self._async_installation_store: Optional[ AsyncInstallationStore] = installation_store self._async_oauth_state_store: Optional[ AsyncOAuthStateStore] = oauth_state_store self._oauth_state_cookie_name = oauth_state_cookie_name self._oauth_state_expiration_seconds = oauth_state_expiration_seconds self._async_oauth_flow: Optional[AsyncOAuthFlow] = None if oauth_flow: self._async_oauth_flow = oauth_flow if self._async_installation_store is None: self._async_installation_store = ( self._async_oauth_flow.installation_store) if self._async_oauth_state_store is None: self._async_oauth_state_store = self._async_oauth_flow.oauth_state_store if self._async_oauth_flow._async_client is None: self._async_oauth_flow._async_client = self._async_client else: if client_id is not None and client_secret is not None: # The OAuth flow support is enabled if (self._async_installation_store is None and self._async_oauth_state_store is None): # use the default ones self._async_installation_store = FileInstallationStore( client_id=client_id, ) self._async_oauth_state_store = FileOAuthStateStore( expiration_seconds=self. _oauth_state_expiration_seconds, client_id=client_id, ) if (self._async_installation_store is not None and self._async_oauth_state_store is None): raise ValueError( f"Configure an appropriate OAuthStateStore for {self._async_installation_store}" ) self._async_oauth_flow = AsyncOAuthFlow( client=create_async_web_client(), logger=self._framework_logger, # required storage implementations installation_store=self._async_installation_store, oauth_state_store=self._async_oauth_state_store, oauth_state_cookie_name=self._oauth_state_cookie_name, oauth_state_expiration_seconds=self. _oauth_state_expiration_seconds, # used for oauth.v2.access calls client_id=client_id, client_secret=client_secret, # installation url parameters scopes=scopes, user_scopes=user_scopes, redirect_uri=redirect_uri, # path in this app install_path=oauth_install_path, redirect_uri_path=oauth_redirect_uri_path, # urls after callback success_url=oauth_success_url, failure_url=oauth_failure_url, ) if self._async_installation_store is not None and self._token is not None: self._token = None self._framework_logger.warning( "As you gave installation_store as well, the bot token will be unused." ) self._async_middleware_list: List[Union[Callable, AsyncMiddleware]] = [] self._async_listeners: List[AsyncListener] = [] self._process_before_response = process_before_response self._init_middleware_list_done = False self._init_async_middleware_list()
def __init__( self, *, logger: Optional[logging.Logger] = None, # Used in logger name: Optional[str] = None, # Set True when you run this app on a FaaS platform process_before_response: bool = False, # Basic Information > Credentials > Signing Secret signing_secret: Optional[str] = None, # for single-workspace apps token: Optional[str] = None, token_verification_enabled: bool = True, client: Optional[WebClient] = None, # for multi-workspace apps authorize: Optional[Callable[..., AuthorizeResult]] = None, installation_store: Optional[InstallationStore] = None, # for v1.0.x compatibility installation_store_bot_only: Optional[bool] = None, # for the OAuth flow oauth_settings: Optional[OAuthSettings] = None, oauth_flow: Optional[OAuthFlow] = None, # No need to set (the value is used only in response to ssl_check requests) verification_token: Optional[str] = None, ): """Bolt App that provides functionalities to register middleware/listeners :param name: The application name that will be used in logging. If absent, the source file name will be used instead. :param process_before_response: True if this app runs on Function as a Service. (Default: False) :param signing_secret: The Signing Secret value used for verifying requests from Slack. :param token: The bot access token required only for single-workspace app. :param token_verification_enabled: Verifies the validity of the given token if True. :param client: The singleton slack_sdk.WebClient instance for this app. :param authorize: The function to authorize an incoming request from Slack by checking if there is a team/user in the installation data. :param installation_store: The module offering save/find operations of installation data :param installation_store_bot_only: Use InstallationStore#find_bot if True (Default: False) :param oauth_settings: The settings related to Slack app installation flow (OAuth flow) :param oauth_flow: Manually instantiated slack_bolt.oauth.OAuthFlow. This is always prioritized over oauth_settings. :param verification_token: Deprecated verification mechanism. This can used only for ssl_check requests. """ signing_secret = signing_secret or os.environ.get( "SLACK_SIGNING_SECRET") token = token or os.environ.get("SLACK_BOT_TOKEN") self._name: str = name or inspect.stack()[1].filename.split( os.path.sep)[-1] self._signing_secret: str = signing_secret self._verification_token: Optional[ str] = verification_token or os.environ.get( "SLACK_VERIFICATION_TOKEN", None) self._framework_logger = logger or get_bolt_logger(App) self._token: Optional[str] = token if client is not None: if not isinstance(client, WebClient): raise BoltError(error_client_invalid_type()) self._client = client self._token = client.token if token is not None: self._framework_logger.warning( warning_client_prioritized_and_token_skipped()) else: self._client = create_web_client( token) # NOTE: the token here can be None # -------------------------------------- # Authorize & OAuthFlow initialization # -------------------------------------- self._authorize: Optional[Authorize] = None if authorize is not None: if oauth_settings is not None or oauth_flow is not None: raise BoltError(error_authorize_conflicts()) self._authorize = CallableAuthorize(logger=self._framework_logger, func=authorize) self._installation_store: Optional[ InstallationStore] = installation_store if self._installation_store is not None and self._authorize is None: self._authorize = InstallationStoreAuthorize( installation_store=self._installation_store, logger=self._framework_logger, bot_only=installation_store_bot_only, ) self._oauth_flow: Optional[OAuthFlow] = None if (oauth_settings is None and os.environ.get("SLACK_CLIENT_ID") is not None and os.environ.get("SLACK_CLIENT_SECRET") is not None): # initialize with the default settings oauth_settings = OAuthSettings() if oauth_flow: self._oauth_flow = oauth_flow installation_store = select_consistent_installation_store( client_id=self._oauth_flow.client_id, app_store=self._installation_store, oauth_flow_store=self._oauth_flow.settings.installation_store, logger=self._framework_logger, ) self._installation_store = installation_store self._oauth_flow.settings.installation_store = installation_store if self._oauth_flow._client is None: self._oauth_flow._client = self._client if self._authorize is None: self._authorize = self._oauth_flow.settings.authorize elif oauth_settings is not None: installation_store = select_consistent_installation_store( client_id=oauth_settings.client_id, app_store=self._installation_store, oauth_flow_store=oauth_settings.installation_store, logger=self._framework_logger, ) self._installation_store = installation_store oauth_settings.installation_store = installation_store self._oauth_flow = OAuthFlow(client=self.client, logger=self.logger, settings=oauth_settings) if self._authorize is None: self._authorize = self._oauth_flow.settings.authorize if (self._installation_store is not None or self._authorize is not None) and self._token is not None: self._token = None self._framework_logger.warning(warning_token_skipped()) # after setting bot_only here, __init__ cannot replace authorize function if installation_store_bot_only is not None and self._oauth_flow is not None: app_bot_only = installation_store_bot_only or False oauth_flow_bot_only = self._oauth_flow.settings.installation_store_bot_only if app_bot_only != oauth_flow_bot_only: self.logger.warning(warning_bot_only_conflicts()) self._oauth_flow.settings.installation_store_bot_only = app_bot_only self._authorize.bot_only = app_bot_only # -------------------------------------- # Middleware Initialization # -------------------------------------- self._middleware_list: List[Union[Callable, Middleware]] = [] self._listeners: List[Listener] = [] listener_executor = ThreadPoolExecutor(max_workers=5) self._listener_runner = ThreadListenerRunner( logger=self._framework_logger, process_before_response=process_before_response, listener_error_handler=DefaultListenerErrorHandler( logger=self._framework_logger), listener_executor=listener_executor, lazy_listener_runner=ThreadLazyListenerRunner( logger=self._framework_logger, executor=listener_executor, ), ) self._init_middleware_list_done = False self._init_middleware_list( token_verification_enabled=token_verification_enabled)
def __init__( self, *, logger: Optional[logging.Logger] = None, # Used in logger name: Optional[str] = None, # Set True when you run this app on a FaaS platform process_before_response: bool = False, # Basic Information > Credentials > Signing Secret signing_secret: Optional[str] = None, # for single-workspace apps token: Optional[str] = None, client: Optional[AsyncWebClient] = None, # for multi-workspace apps installation_store: Optional[AsyncInstallationStore] = None, authorize: Optional[Callable[..., Awaitable[AuthorizeResult]]] = None, # for the OAuth flow oauth_settings: Optional[AsyncOAuthSettings] = None, oauth_flow: Optional[AsyncOAuthFlow] = None, # No need to set (the value is used only in response to ssl_check requests) verification_token: Optional[str] = None, ): """Bolt App that provides functionalities to register middleware/listeners :param name: The application name that will be used in logging. If absent, the source file name will be used instead. :param process_before_response: True if this app runs on Function as a Service. (Default: False) :param signing_secret: The Signing Secret value used for verifying requests from Slack. :param token: The bot access token required only for single-workspace app. :param client: The singleton slack_sdk.web.async_client.AsyncWebClient instance for this app. :param installation_store: The module offering save/find operations of installation data :param authorize: The function to authorize an incoming request from Slack by checking if there is a team/user in the installation data. :param oauth_settings: The settings related to Slack app installation flow (OAuth flow) :param oauth_flow: Manually instantiated slack_bolt.oauth.async_oauth_flow.AsyncOAuthFlow. This is always prioritized over oauth_settings. :param verification_token: Deprecated verification mechanism. This can used only for ssl_check requests. """ signing_secret = signing_secret or os.environ.get( "SLACK_SIGNING_SECRET") token = token or os.environ.get("SLACK_BOT_TOKEN") self._name: str = name or inspect.stack()[1].filename.split( os.path.sep)[-1] self._signing_secret: str = signing_secret self._verification_token: Optional[ str] = verification_token or os.environ.get( "SLACK_VERIFICATION_TOKEN", None) self._framework_logger = logger or get_bolt_logger(AsyncApp) self._token: Optional[str] = token if client is not None: if not isinstance(client, AsyncWebClient): raise BoltError(error_client_invalid_type_async()) self._async_client = client self._token = client.token if token is not None: self._framework_logger.warning( warning_client_prioritized_and_token_skipped()) else: # NOTE: the token here can be None self._async_client = create_async_web_client(token) self._async_authorize: Optional[AsyncAuthorize] = None if authorize is not None: if oauth_settings is not None or oauth_flow is not None: raise BoltError(error_authorize_conflicts()) self._async_authorize = AsyncCallableAuthorize( logger=self._framework_logger, func=authorize) self._async_installation_store: Optional[ AsyncInstallationStore] = installation_store if self._async_installation_store is not None and self._async_authorize is None: self._async_authorize = AsyncInstallationStoreAuthorize( installation_store=self._async_installation_store, logger=self._framework_logger, ) self._async_oauth_flow: Optional[AsyncOAuthFlow] = None if (oauth_settings is None and os.environ.get("SLACK_CLIENT_ID") is not None and os.environ.get("SLACK_CLIENT_SECRET") is not None): # initialize with the default settings 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, app_store=self._async_installation_store, oauth_flow_store=self._async_oauth_flow.settings. installation_store, logger=self._framework_logger, ) self._async_installation_store = installation_store self._async_oauth_flow.settings.installation_store = installation_store if self._async_oauth_flow._async_client is None: self._async_oauth_flow._async_client = self._async_client 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, oauth_flow_store=oauth_settings.installation_store, logger=self._framework_logger, ) self._async_installation_store = installation_store oauth_settings.installation_store = installation_store self._async_oauth_flow = AsyncOAuthFlow(client=self._async_client, logger=self.logger, settings=oauth_settings) if self._async_authorize is None: self._async_authorize = self._async_oauth_flow.settings.authorize if (self._async_installation_store is not None or self._async_authorize is not None) and self._token is not None: self._token = None self._framework_logger.warning(warning_token_skipped()) self._async_middleware_list: List[Union[Callable, AsyncMiddleware]] = [] self._async_listeners: List[AsyncListener] = [] self._async_listener_runner = AsyncioListenerRunner( logger=self._framework_logger, process_before_response=process_before_response, listener_error_handler=AsyncDefaultListenerErrorHandler( logger=self._framework_logger), lazy_listener_runner=AsyncioLazyListenerRunner( logger=self._framework_logger, ), ) self._init_middleware_list_done = False self._init_async_middleware_list() self._server: Optional[AsyncSlackAppServer] = None
def convert_to_dict(obj: Union[Dict, JsonObject]) -> Dict: if isinstance(obj, dict): return obj if isinstance(obj, JsonObject) or hasattr(obj, "to_dict"): return obj.to_dict() raise BoltError(f"{obj} (type: {type(obj)}) is unsupported")