Exemplo n.º 1
0
    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
Exemplo n.º 2
0
    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))
Exemplo n.º 3
0
    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
Exemplo n.º 4
0
    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))
Exemplo n.º 5
0
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"
    )
Exemplo n.º 6
0
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"
    )
Exemplo n.º 7
0
    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,
        )
Exemplo n.º 8
0
    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
Exemplo n.º 9
0
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"
    )
Exemplo n.º 10
0
    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})"
            )
Exemplo n.º 11
0
    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
Exemplo n.º 12
0
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"
        )
Exemplo n.º 13
0
    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
Exemplo n.º 14
0
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"
    )
Exemplo n.º 15
0
    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
Exemplo n.º 16
0
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"
        )
Exemplo n.º 17
0
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"
    )
Exemplo n.º 18
0
    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,
            ))
Exemplo n.º 19
0
    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
Exemplo n.º 20
0
    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
Exemplo n.º 21
0
    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
Exemplo n.º 22
0
    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")
Exemplo n.º 23
0
    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,
        )
Exemplo n.º 24
0
    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()
Exemplo n.º 25
0
    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)
Exemplo n.º 26
0
    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
Exemplo n.º 27
0
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")