Skip to content

Commit

Permalink
Fix #1074 Customize user-facing message sent when an installation is …
Browse files Browse the repository at this point in the history
…not managed by bolt-python app (#1077)
  • Loading branch information
seratch committed May 9, 2024
1 parent e5131c9 commit dbe2333
Show file tree
Hide file tree
Showing 11 changed files with 111 additions and 42 deletions.
14 changes: 7 additions & 7 deletions scripts/install_all_and_run_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,24 @@ python_version=`python --version | awk '{print $2}'`

if [ ${python_version:0:3} == "3.6" ]
then
pip install -r requirements.txt
pip install -U -r requirements.txt
else
pip install -e .
fi

if [[ $test_target != "" ]]
then
pip install -r requirements/testing.txt && \
pip install -r requirements/adapter.txt && \
pip install -r requirements/adapter_testing.txt && \
pip install -U -r requirements/testing.txt && \
pip install -U -r requirements/adapter.txt && \
pip install -U -r requirements/adapter_testing.txt && \
# To avoid errors due to the old versions of click forced by Chalice
pip install -U pip click && \
black slack_bolt/ tests/ && \
pytest $1
else
pip install -r requirements/testing.txt && \
pip install -r requirements/adapter.txt && \
pip install -r requirements/adapter_testing.txt && \
pip install -U -r requirements/testing.txt && \
pip install -U -r requirements/adapter.txt && \
pip install -U -r requirements/adapter_testing.txt && \
# To avoid errors due to the old versions of click forced by Chalice
pip install -U pip click && \
black slack_bolt/ tests/ && \
Expand Down
15 changes: 13 additions & 2 deletions slack_bolt/app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ def __init__(
# for multi-workspace apps
before_authorize: Optional[Union[Middleware, Callable[..., Any]]] = None,
authorize: Optional[Callable[..., AuthorizeResult]] = None,
user_facing_authorize_error_message: Optional[str] = None,
installation_store: Optional[InstallationStore] = None,
# for either only bot scope usage or v1.0.x compatibility
installation_store_bot_only: Optional[bool] = None,
Expand Down Expand Up @@ -159,6 +160,8 @@ def message_hello(message, say):
before_authorize: A global middleware that can be executed right before authorize function
authorize: The function to authorize an incoming request from Slack
by checking if there is a team/user in the installation data.
user_facing_authorize_error_message: The user-facing error message to display
when the app is installed but the installation is not managed by this app's installation store
installation_store: The module offering save/find operations of installation data
installation_store_bot_only: Use `InstallationStore#find_bot()` if True (Default: False)
request_verification_enabled: False if you would like to disable the built-in middleware (Default: True).
Expand All @@ -178,7 +181,7 @@ def message_hello(message, say):
`SslCheck` is a built-in middleware that handles ssl_check requests from Slack.
oauth_settings: The settings related to Slack app installation flow (OAuth flow)
oauth_flow: Instantiated `slack_bolt.oauth.OAuthFlow`. This is always prioritized over oauth_settings.
verification_token: Deprecated verification mechanism. This can used only for ssl_check requests.
verification_token: Deprecated verification mechanism. This can be used only for ssl_check requests.
listener_executor: Custom executor to run background tasks. If absent, the default `ThreadPoolExecutor` will
be used.
"""
Expand Down Expand Up @@ -348,6 +351,7 @@ def message_hello(message, say):
ignoring_self_events_enabled=ignoring_self_events_enabled,
ssl_check_enabled=ssl_check_enabled,
url_verification_enabled=url_verification_enabled,
user_facing_authorize_error_message=user_facing_authorize_error_message,
)

def _init_middleware_list(
Expand All @@ -357,6 +361,7 @@ def _init_middleware_list(
ignoring_self_events_enabled: bool = True,
ssl_check_enabled: bool = True,
url_verification_enabled: bool = True,
user_facing_authorize_error_message: Optional[str] = None,
):
if self._init_middleware_list_done:
return
Expand Down Expand Up @@ -385,13 +390,18 @@ def _init_middleware_list(
SingleTeamAuthorization(
auth_test_result=auth_test_result,
base_logger=self._base_logger,
user_facing_authorize_error_message=user_facing_authorize_error_message,
)
)
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, base_logger=self._base_logger)
MultiTeamsAuthorization(
authorize=self._authorize,
base_logger=self._base_logger,
user_facing_authorize_error_message=user_facing_authorize_error_message,
)
)
else:
raise BoltError(error_token_required())
Expand All @@ -401,6 +411,7 @@ def _init_middleware_list(
authorize=self._authorize,
base_logger=self._base_logger,
user_token_resolution=self._oauth_flow.settings.user_token_resolution,
user_facing_authorize_error_message=user_facing_authorize_error_message,
)
)
if ignoring_self_events_enabled is True:
Expand Down
19 changes: 17 additions & 2 deletions slack_bolt/app/async_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ def __init__(
# for multi-workspace apps
before_authorize: Optional[Union[AsyncMiddleware, Callable[..., Awaitable[Any]]]] = None,
authorize: Optional[Callable[..., Awaitable[AuthorizeResult]]] = None,
user_facing_authorize_error_message: Optional[str] = None,
installation_store: Optional[AsyncInstallationStore] = None,
# for either only bot scope usage or v1.0.x compatibility
installation_store_bot_only: Optional[bool] = None,
Expand Down Expand Up @@ -167,6 +168,8 @@ async def message_hello(message, say): # async function
before_authorize: A global middleware that can be executed right before authorize function
authorize: The function to authorize an incoming request from Slack
by checking if there is a team/user in the installation data.
user_facing_authorize_error_message: The user-facing error message to display
when the app is installed but the installation is not managed by this app's installation store
installation_store: The module offering save/find operations of installation data
installation_store_bot_only: Use `AsyncInstallationStore#async_find_bot()` if True (Default: False)
request_verification_enabled: False if you would like to disable the built-in middleware (Default: True).
Expand Down Expand Up @@ -354,6 +357,7 @@ async def message_hello(message, say): # async function
ignoring_self_events_enabled=ignoring_self_events_enabled,
ssl_check_enabled=ssl_check_enabled,
url_verification_enabled=url_verification_enabled,
user_facing_authorize_error_message=user_facing_authorize_error_message,
)

self._server: Optional[AsyncSlackAppServer] = None
Expand All @@ -364,6 +368,7 @@ def _init_async_middleware_list(
ignoring_self_events_enabled: bool = True,
ssl_check_enabled: bool = True,
url_verification_enabled: bool = True,
user_facing_authorize_error_message: Optional[str] = None,
):
if self._init_middleware_list_done:
return
Expand All @@ -383,10 +388,19 @@ def _init_async_middleware_list(
# As authorize is required for making a Bolt app function, we don't offer the flag to disable this
if self._async_oauth_flow is None:
if self._token:
self._async_middleware_list.append(AsyncSingleTeamAuthorization(base_logger=self._base_logger))
self._async_middleware_list.append(
AsyncSingleTeamAuthorization(
base_logger=self._base_logger,
user_facing_authorize_error_message=user_facing_authorize_error_message,
)
)
elif self._async_authorize is not None:
self._async_middleware_list.append(
AsyncMultiTeamsAuthorization(authorize=self._async_authorize, base_logger=self._base_logger)
AsyncMultiTeamsAuthorization(
authorize=self._async_authorize,
base_logger=self._base_logger,
user_facing_authorize_error_message=user_facing_authorize_error_message,
)
)
else:
raise BoltError(error_token_required())
Expand All @@ -396,6 +410,7 @@ def _init_async_middleware_list(
authorize=self._async_authorize,
base_logger=self._base_logger,
user_token_resolution=self._async_oauth_flow.settings.user_token_resolution,
user_facing_authorize_error_message=user_facing_authorize_error_message,
)
)

Expand Down
5 changes: 2 additions & 3 deletions slack_bolt/middleware/authorization/async_internals.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from slack_bolt.middleware.authorization.internals import _build_error_text
from slack_bolt.request.async_request import AsyncBoltRequest
from slack_bolt.response import BoltResponse

Expand All @@ -15,9 +14,9 @@ def _is_no_auth_required(req: AsyncBoltRequest) -> bool:
return _is_url_verification(req) or _is_ssl_check(req)


def _build_error_response() -> BoltResponse:
def _build_user_facing_error_response(message: str) -> BoltResponse:
# show an ephemeral message to the end-user
return BoltResponse(
status=200,
body=_build_error_text(),
body=message,
)
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
from slack_bolt.request.async_request import AsyncBoltRequest
from slack_bolt.response import BoltResponse
from .async_authorization import AsyncAuthorization
from .async_internals import _build_error_response, _is_no_auth_required
from .internals import _is_no_auth_test_call_required, _build_error_text
from .async_internals import _build_user_facing_error_response, _is_no_auth_required
from .internals import _is_no_auth_test_call_required, _build_user_facing_authorize_error_message
from ...authorization import AuthorizeResult
from ...authorization.async_authorize import AsyncAuthorize

Expand All @@ -21,17 +21,22 @@ def __init__(
authorize: AsyncAuthorize,
base_logger: Optional[Logger] = None,
user_token_resolution: str = "authed_user",
user_facing_authorize_error_message: Optional[str] = None,
):
"""Multi-workspace authorization.
Args:
authorize: The function to authorize incoming requests from Slack.
base_logger: The base logger
user_token_resolution: "authed_user" or "actor"
user_facing_authorize_error_message: The user-facing error message when installation is not found
"""
self.authorize = authorize
self.logger = get_bolt_logger(AsyncMultiTeamsAuthorization, base_logger=base_logger)
self.user_token_resolution = user_token_resolution
self.user_facing_authorize_error_message = (
user_facing_authorize_error_message or _build_user_facing_authorize_error_message()
)

async def async_process(
self,
Expand Down Expand Up @@ -92,10 +97,10 @@ async def async_process(
"the AuthorizeResult (returned value from authorize) for it was not found."
)
if req.context.response_url is not None:
await req.context.respond(_build_error_text())
await req.context.respond(self.user_facing_authorize_error_message)
return BoltResponse(status=200, body="")
return _build_error_response()
return _build_user_facing_error_response(self.user_facing_authorize_error_message)

except SlackApiError as e:
self.logger.error(f"Failed to authorize with the given token ({e})")
return _build_error_response()
return _build_user_facing_error_response(self.user_facing_authorize_error_message)
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,23 @@
from slack_bolt.response import BoltResponse
from slack_sdk.web.async_slack_response import AsyncSlackResponse
from slack_sdk.errors import SlackApiError
from .async_internals import _build_error_response, _is_no_auth_required
from .internals import _to_authorize_result, _is_no_auth_test_call_required, _build_error_text
from .async_internals import _build_user_facing_error_response, _is_no_auth_required
from .internals import _to_authorize_result, _is_no_auth_test_call_required, _build_user_facing_authorize_error_message
from ...authorization import AuthorizeResult


class AsyncSingleTeamAuthorization(AsyncAuthorization):
def __init__(self, base_logger: Optional[Logger] = None):
def __init__(
self,
base_logger: Optional[Logger] = None,
user_facing_authorize_error_message: Optional[str] = None,
):
"""Single-workspace authorization."""
self.auth_test_result: Optional[AsyncSlackResponse] = None
self.logger = get_bolt_logger(AsyncSingleTeamAuthorization, base_logger=base_logger)
self.user_facing_authorize_error_message = (
user_facing_authorize_error_message or _build_user_facing_authorize_error_message()
)

async def async_process(
self,
Expand Down Expand Up @@ -58,9 +65,9 @@ async def async_process(
# Just in case
self.logger.error("auth.test API call result is unexpectedly None")
if req.context.response_url is not None:
await req.context.respond(_build_error_text())
await req.context.respond(self.user_facing_authorize_error_message)
return BoltResponse(status=200, body="")
return _build_error_response()
return _build_user_facing_error_response(self.user_facing_authorize_error_message)
except SlackApiError as e:
self.logger.error(f"Failed to authorize with the given token ({e})")
return _build_error_response()
return _build_user_facing_error_response(self.user_facing_authorize_error_message)
6 changes: 3 additions & 3 deletions slack_bolt/middleware/authorization/internals.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,18 @@ def _is_no_auth_test_call_required(req: Union[BoltRequest, "AsyncBoltRequest"])
return _is_no_auth_test_events(req)


def _build_error_text() -> str:
def _build_user_facing_authorize_error_message() -> str:
return (
":warning: We apologize, but for some unknown reason, your installation with this app is no longer available. "
"Please reinstall this app into your workspace :bow:"
)


def _build_error_response() -> BoltResponse:
def _build_user_facing_error_response(message: str) -> BoltResponse:
# show an ephemeral message to the end-user
return BoltResponse(
status=200,
body=_build_error_text(),
body=message,
)


Expand Down
15 changes: 10 additions & 5 deletions slack_bolt/middleware/authorization/multi_teams_authorization.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
from slack_bolt.response import BoltResponse
from .authorization import Authorization
from .internals import (
_build_error_response,
_build_user_facing_error_response,
_is_no_auth_required,
_is_no_auth_test_call_required,
_build_error_text,
_build_user_facing_authorize_error_message,
)
from ...authorization import AuthorizeResult
from ...authorization.authorize import Authorize
Expand All @@ -27,17 +27,22 @@ def __init__(
authorize: Authorize,
base_logger: Optional[Logger] = None,
user_token_resolution: str = "authed_user",
user_facing_authorize_error_message: Optional[str] = None,
):
"""Multi-workspace authorization.
Args:
authorize: The function to authorize incoming requests from Slack.
base_logger: The base logger
user_token_resolution: "authed_user" or "actor"
user_facing_authorize_error_message: The user-facing error message when installation is not found
"""
self.authorize = authorize
self.logger = get_bolt_logger(MultiTeamsAuthorization, base_logger=base_logger)
self.user_token_resolution = user_token_resolution
self.user_facing_authorize_error_message = (
user_facing_authorize_error_message or _build_user_facing_authorize_error_message()
)

def process(
self,
Expand Down Expand Up @@ -95,10 +100,10 @@ def process(
"the AuthorizeResult (returned value from authorize) for it was not found."
)
if req.context.response_url is not None:
req.context.respond(_build_error_text())
req.context.respond(self.user_facing_authorize_error_message)
return BoltResponse(status=200, body="")
return _build_error_response()
return _build_user_facing_error_response(self.user_facing_authorize_error_message)

except SlackApiError as e:
self.logger.error(f"Failed to authorize with the given token ({e})")
return _build_error_response()
return _build_user_facing_error_response(self.user_facing_authorize_error_message)
14 changes: 9 additions & 5 deletions slack_bolt/middleware/authorization/single_team_authorization.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
from slack_sdk.errors import SlackApiError
from slack_sdk.web import SlackResponse
from .internals import (
_build_error_response,
_build_user_facing_error_response,
_is_no_auth_required,
_to_authorize_result,
_is_no_auth_test_call_required,
_build_error_text,
_build_user_facing_authorize_error_message,
)
from ...authorization import AuthorizeResult

Expand All @@ -23,6 +23,7 @@ def __init__(
*,
auth_test_result: Optional[SlackResponse] = None,
base_logger: Optional[Logger] = None,
user_facing_authorize_error_message: Optional[str] = None,
):
"""Single-workspace authorization.
Expand All @@ -32,6 +33,9 @@ def __init__(
"""
self.auth_test_result = auth_test_result
self.logger = get_bolt_logger(SingleTeamAuthorization, base_logger=base_logger)
self.user_facing_authorize_error_message = (
user_facing_authorize_error_message or _build_user_facing_authorize_error_message()
)

def process(
self,
Expand Down Expand Up @@ -73,9 +77,9 @@ def process(
# Just in case
self.logger.error("auth.test API call result is unexpectedly None")
if req.context.response_url is not None:
req.context.respond(_build_error_text())
req.context.respond(self.user_facing_authorize_error_message)
return BoltResponse(status=200, body="")
return _build_error_response()
return _build_user_facing_error_response(self.user_facing_authorize_error_message)
except SlackApiError as e:
self.logger.error(f"Failed to authorize with the given token ({e})")
return _build_error_response()
return _build_user_facing_error_response(self.user_facing_authorize_error_message)

0 comments on commit dbe2333

Please sign in to comment.