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