Beispiel #1
0
    def test_installation_store_bot_only(self):
        installation_store = BotOnlyMemoryInstallationStore()
        authorize = InstallationStoreAuthorize(
            logger=installation_store.logger,
            installation_store=installation_store,
            bot_only=True,
        )
        assert authorize.find_installation_available is True
        assert authorize.bot_only is True
        context = BoltContext()
        context["client"] = WebClient(base_url=self.mock_api_server_base_url)
        result = authorize(context=context,
                           enterprise_id="E111",
                           team_id="T0G9PQBBK",
                           user_id="W11111")
        assert authorize.find_installation_available is True
        assert result.bot_id == "BZYBOTHED"
        assert result.bot_user_id == "W23456789"
        assert result.user_token is None
        assert self.mock_received_requests["/auth.test"] == 1

        result = authorize(context=context,
                           enterprise_id="E111",
                           team_id="T0G9PQBBK",
                           user_id="W11111")
        assert result.bot_id == "BZYBOTHED"
        assert result.bot_user_id == "W23456789"
        assert result.user_token is None
        assert self.mock_received_requests["/auth.test"] == 2
Beispiel #2
0
    def __init__(
            self,
            *,
            client: Optional[WebClient] = None,
            logger: Optional[Logger] = None,
            settings: Optional[OAuthSettings] = None,
            oauth_state_bucket_name: Optional[str] = None,  # required
            installation_bucket_name: Optional[str] = None,  # required
    ):
        logger = logger or logging.getLogger(__name__)
        settings = settings or OAuthSettings(
            client_id=os.environ["SLACK_CLIENT_ID"],
            client_secret=os.environ["SLACK_CLIENT_SECRET"],
        )
        oauth_state_bucket_name = (oauth_state_bucket_name
                                   or os.environ["SLACK_STATE_S3_BUCKET_NAME"])
        installation_bucket_name = (
            installation_bucket_name
            or os.environ["SLACK_INSTALLATION_S3_BUCKET_NAME"])
        self.s3_client = boto3.client("s3")
        if settings.state_store is None or not isinstance(
                settings.state_store, AmazonS3OAuthStateStore):
            settings.state_store = AmazonS3OAuthStateStore(
                logger=logger,
                s3_client=self.s3_client,
                bucket_name=oauth_state_bucket_name,
                expiration_seconds=settings.state_expiration_seconds,
            )

        if settings.installation_store is None or not isinstance(
                settings.installation_store, AmazonS3InstallationStore):
            settings.installation_store = AmazonS3InstallationStore(
                logger=logger,
                s3_client=self.s3_client,
                bucket_name=installation_bucket_name,
                client_id=settings.client_id,
            )

        # Set up authorize function to surely use this installation_store.
        # When a developer use a settings initialized outside this constructor,
        # the settings may already have pre-defined authorize.
        # In this case, the /slack/events endpoint doesn't work along with the OAuth flow.
        settings.authorize = InstallationStoreAuthorize(
            logger=logger,
            installation_store=settings.installation_store,
            bot_only=settings.installation_store_bot_only,
        )

        OAuthFlow.__init__(self,
                           client=client,
                           logger=logger,
                           settings=settings)
Beispiel #3
0
    def test_installation_store(self):
        installation_store = MemoryInstallationStore()
        authorize = InstallationStoreAuthorize(
            logger=installation_store.logger, installation_store=installation_store
        )
        context = BoltContext()
        context["client"] = WebClient(base_url=self.mock_api_server_base_url)
        result = authorize(
            context=context, enterprise_id="E111", team_id="T0G9PQBBK", user_id="W11111"
        )
        assert result.bot_id == "BZYBOTHED"
        assert result.bot_user_id == "W23456789"
        assert self.mock_received_requests["/auth.test"] == 1

        result = authorize(
            context=context, enterprise_id="E111", team_id="T0G9PQBBK", user_id="W11111"
        )
        assert result.bot_id == "BZYBOTHED"
        assert result.bot_user_id == "W23456789"
        assert self.mock_received_requests["/auth.test"] == 2
Beispiel #4
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,
        )
Beispiel #5
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[WebClient] = None,
        # for multi-workspace apps
        authorize: Optional[Callable[..., AuthorizeResult]] = None,
        installation_store: Optional[InstallationStore] = 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 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 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

        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,
            )

        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())

        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()