def test_installation_store_conflicts(self): store1 = FileInstallationStore() store2 = FileInstallationStore() app = App( signing_secret="valid", oauth_settings=OAuthSettings( client_id="111.222", client_secret="valid", installation_store=store1, ), installation_store=store2, ) assert app.installation_store is store1 app = App( signing_secret="valid", oauth_flow=OAuthFlow(settings=OAuthSettings( client_id="111.222", client_secret="valid", installation_store=store1, )), installation_store=store2, ) assert app.installation_store is store1 app = App( signing_secret="valid", oauth_flow=OAuthFlow(settings=OAuthSettings( client_id="111.222", client_secret="valid", )), installation_store=store1, ) assert app.installation_store is store1
def setup_server(cls): cls.old_os_env = remove_os_env_temporarily() signing_secret = "secret" valid_token = "xoxb-valid" mock_api_server_base_url = "http://localhost:8888" web_client = WebClient(token=valid_token, base_url=mock_api_server_base_url,) app = App( client=web_client, signing_secret=signing_secret, oauth_settings=OAuthSettings( client_id="111.111", client_secret="xxx", scopes=["chat:write", "commands"], ), ) handler = SlackRequestHandler(app) class SlackApp(object): @cherrypy.expose @cherrypy.tools.slack_in() def install(self, **kwargs): return handler.handle() cherrypy.tree.mount(SlackApp(), "/slack")
def __init__( self, *, client: Optional[WebClient] = None, logger: Optional[Logger] = None, settings: OAuthSettings, ): """The module to run the Slack app installation flow (OAuth flow). :param client: The WebClient. :param logger: The logger. :param settings: OAuth settings to configure this module. """ self._client = client self._logger = logger 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 = DefaultCallbackOptions( logger=logger, state_utils=self.settings.state_utils, redirect_uri_page_renderer=self.settings. redirect_uri_page_renderer, ) if settings.callback_options is None: settings.callback_options = self.default_callback_options self.success_handler = settings.callback_options.success self.failure_handler = settings.callback_options.failure
def test_authorize_conflicts(self): oauth_settings = OAuthSettings( client_id="111.222", client_secret="valid", installation_store=FileInstallationStore(), state_store=FileOAuthStateStore(expiration_seconds=120), ) # no error with this App(signing_secret="valid", oauth_settings=oauth_settings) def authorize() -> AuthorizeResult: return AuthorizeResult(enterprise_id="E111", team_id="T111") with pytest.raises(BoltError): App( signing_secret="valid", authorize=authorize, oauth_settings=oauth_settings, ) oauth_flow = OAuthFlow(settings=oauth_settings) # no error with this App(signing_secret="valid", oauth_flow=oauth_flow) with pytest.raises(BoltError): App(signing_secret="valid", authorize=authorize, oauth_flow=oauth_flow)
def test_oauth(self): app = App( client=self.web_client, signing_secret=self.signing_secret, oauth_settings=OAuthSettings( client_id="111.111", client_secret="xxx", scopes=["chat:write", "commands"], ), ) app_handler = SlackRequestHandler(app) async def endpoint(req: Request): return await app_handler.handle(req) api = Starlette( debug=True, routes=[ Route("/slack/install", endpoint=endpoint, methods=["GET"]) ], ) client = TestClient(api) response = client.get("/slack/install", allow_redirects=False) assert response.status_code == 200 assert response.headers.get( "content-type") == "text/html; charset=utf-8" assert "https://slack.com/oauth/v2/authorize?state=" in response.text
def test_valid_multi_auth(self): app = App( signing_secret="valid", oauth_settings=OAuthSettings(client_id="111.222", client_secret="valid"), ) assert app != None
async def test_instantiation_non_async_settings(self): with pytest.raises(BoltError): AsyncOAuthFlow(settings=OAuthSettings( client_id="111.222", client_secret="xxx", scopes="chat:write,commands", ))
def test_oauth(self): app = App( client=self.web_client, signing_secret=self.signing_secret, oauth_settings=OAuthSettings( client_id="111.111", client_secret="xxx", scopes=["chat:write", "commands"], ), ) chalice_app = Chalice(app_name="bolt-python-chalice") slack_handler = ChaliceSlackRequestHandler(app=app, chalice=chalice_app) @chalice_app.route("/slack/install", methods=["GET"]) def install() -> Response: return slack_handler.handle(chalice_app.current_request) response: Dict[str, Any] = LocalGateway( chalice_app, Config()).handle_request(method="GET", path="/slack/install", body="", headers={}) assert response["statusCode"] == 200 assert response["headers"][ "content-type"] == "text/html; charset=utf-8" assert response["headers"]["content-length"] == "565" assert "https://slack.com/oauth/v2/authorize?state=" in response.get( "body")
def test_valid_multi_auth_secret_absence(self): with pytest.raises(BoltError): App( signing_secret="valid", oauth_settings=OAuthSettings(client_id="111.222", client_secret=None), )
def sqlite3( cls, database: str, # OAuth flow parameters/credentials client_id: Optional[str] = None, # required client_secret: Optional[str] = None, # required scopes: Optional[Sequence[str]] = None, user_scopes: Optional[Sequence[str]] = None, redirect_uri: Optional[str] = None, # Handler configuration install_path: Optional[str] = None, redirect_uri_path: Optional[str] = None, callback_options: Optional[CallbackOptions] = None, success_url: Optional[str] = None, failure_url: Optional[str] = None, authorization_url: Optional[str] = None, # Installation Management # state parameter related configurations state_cookie_name: str = OAuthStateUtils.default_cookie_name, state_expiration_seconds: int = OAuthStateUtils.default_expiration_seconds, client: Optional[WebClient] = None, logger: Optional[Logger] = None, ) -> "OAuthFlow": client_id = client_id or os.environ["SLACK_CLIENT_ID"] # required client_secret = client_secret or os.environ["SLACK_CLIENT_SECRET"] # required 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") return OAuthFlow( client=client or WebClient(), logger=logger, settings=OAuthSettings( # OAuth flow parameters/credentials client_id=client_id, client_secret=client_secret, scopes=scopes, user_scopes=user_scopes, redirect_uri=redirect_uri, # Handler configuration install_path=install_path, redirect_uri_path=redirect_uri_path, callback_options=callback_options, success_url=success_url, failure_url=failure_url, authorization_url=authorization_url, # Installation Management installation_store=SQLite3InstallationStore( database=database, client_id=client_id, logger=logger, ), # state parameter related configurations state_store=SQLite3OAuthStateStore( database=database, expiration_seconds=state_expiration_seconds, logger=logger, ), state_cookie_name=state_cookie_name, state_expiration_seconds=state_expiration_seconds, ), )
def test_handle_callback(self): oauth_flow = OAuthFlow( client=WebClient(base_url=self.mock_api_server_base_url), settings=OAuthSettings( client_id="111.222", client_secret="xxx", scopes=["chat:write", "commands"], installation_store=FileInstallationStore(), state_store=FileOAuthStateStore(expiration_seconds=120), success_url="https://www.example.com/completion", failure_url="https://www.example.com/failure", ), ) state = oauth_flow.issue_new_state(None) req = BoltRequest( body="", query=f"code=foo&state={state}", headers={ "cookie": [f"{oauth_flow.settings.state_cookie_name}={state}"] }, ) resp = oauth_flow.handle_callback(req) assert resp.status == 200 assert "https://www.example.com/completion" in resp.body app = App(signing_secret="signing_secret", oauth_flow=oauth_flow) global_shortcut_body = { "type": "shortcut", "token": "verification_token", "action_ts": "111.111", "team": { "id": "T111", "domain": "workspace-domain", "enterprise_id": "E111", "enterprise_name": "Org Name", }, "user": { "id": "W111", "username": "******", "team_id": "T111" }, "callback_id": "test-shortcut", "trigger_id": "111.111.xxxxxx", } body = f"payload={quote(json.dumps(global_shortcut_body))}" timestamp = str(int(time())) signature_verifier = SignatureVerifier("signing_secret") headers = { "content-type": ["application/x-www-form-urlencoded"], "x-slack-signature": [ signature_verifier.generate_signature(body=body, timestamp=timestamp) ], "x-slack-request-timestamp": [timestamp], } request = BoltRequest(body=body, headers=headers) response = app.dispatch(request) assert response.status == 200 assert self.mock_received_requests["/auth.test"] == 1
def test_scopes_as_str(self): settings = OAuthSettings( client_id="111.222", client_secret="xxx", scopes="chat:write,commands", user_scopes="search:read", ) assert settings.scopes == ["chat:write", "commands"] assert settings.user_scopes == ["search:read"]
def test_valid_multi_auth_oauth_flow(self): oauth_flow = OAuthFlow(settings=OAuthSettings( client_id="111.222", client_secret="valid", installation_store=FileInstallationStore(), state_store=FileOAuthStateStore(expiration_seconds=120), )) app = App(signing_secret="valid", oauth_flow=oauth_flow) assert app != None
async def test_instantiation_non_async_settings_to_app(self): with pytest.raises(BoltError): AsyncApp( signing_secret="xxx", oauth_settings=OAuthSettings( client_id="111.222", client_secret="xxx", scopes="chat:write,commands", ), )
def test_instantiation(self): oauth_flow = OAuthFlow(settings=OAuthSettings( client_id="111.222", client_secret="xxx", scopes=["chat:write", "commands"], installation_store=FileInstallationStore(), state_store=FileOAuthStateStore(expiration_seconds=120), )) assert oauth_flow is not None assert oauth_flow.logger is not None assert oauth_flow.client is not None
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_instantiation(self): oauth_flow = LambdaS3OAuthFlow( settings=OAuthSettings( client_id="111.222", client_secret="xxx", scopes=["chat:write"], ), installation_bucket_name="dummy-installation", oauth_state_bucket_name="dummy-state", ) assert oauth_flow is not None assert oauth_flow.client is not None assert oauth_flow.logger is not None
def test_handle_installation(self): oauth_flow = OAuthFlow(settings=OAuthSettings( client_id="111.222", client_secret="xxx", scopes=["chat:write", "commands"], installation_store=FileInstallationStore(), state_store=FileOAuthStateStore(expiration_seconds=120), )) req = BoltRequest(body="") resp = oauth_flow.handle_installation(req) assert resp.status == 200 assert resp.headers.get("content-type") == ["text/html; charset=utf-8"] assert resp.headers.get("content-length") == ["565"] assert "https://slack.com/oauth/v2/authorize?state=" in resp.body
def test_handle_installation_no_rendering(self): oauth_flow = OAuthFlow(settings=OAuthSettings( client_id="111.222", client_secret="xxx", scopes=["chat:write", "commands"], user_scopes=["search:read"], installation_store=FileInstallationStore(), install_page_rendering_enabled=False, # disabled state_store=FileOAuthStateStore(expiration_seconds=120), )) req = BoltRequest(body="") resp = oauth_flow.handle_installation(req) assert resp.status == 302 location_header = resp.headers.get("location")[0] assert "https://slack.com/oauth/v2/authorize?state=" in location_header
def test_oauth(self): app = App( client=self.web_client, signing_secret=self.signing_secret, oauth_settings=OAuthSettings( client_id="111.111", client_secret="xxx", scopes=["chat:write", "commands"], ), ) request = self.rf.get("/slack/install") response = SlackRequestHandler(app).handle(request) assert response.status_code == 200 assert response.get("content-type") == "text/html; charset=utf-8" assert "https://slack.com/oauth/v2/authorize?state=" in response.content.decode( "utf-8")
def test_oauth(self): app = App( client=self.web_client, signing_secret=self.signing_secret, oauth_settings=OAuthSettings( client_id="111.111", client_secret="xxx", scopes=["chat:write", "commands"], ), ) event = { "body": "", "queryStringParameters": {}, "headers": {}, "requestContext": { "http": { "method": "GET" } }, "isBase64Encoded": False, } response = SlackRequestHandler(app).handle(event, self.context) assert response["statusCode"] == 200 assert response["headers"][ "content-type"] == "text/html; charset=utf-8" assert response["headers"]["content-length"] == "565" assert response.get("body") is not None event = { "body": "", "queryStringParameters": {}, "headers": {}, "requestContext": { "httpMethod": "GET" }, "isBase64Encoded": False, } response = SlackRequestHandler(app).handle(event, self.context) assert response["statusCode"] == 200 assert response["headers"][ "content-type"] == "text/html; charset=utf-8" assert response["headers"]["content-length"] == "565" assert "https://slack.com/oauth/v2/authorize?state=" in response.get( "body")
def test_handle_callback_using_options(self): def success(args: SuccessArgs) -> BoltResponse: assert args.request is not None return BoltResponse(status=200, body="customized") def failure(args: FailureArgs) -> BoltResponse: assert args.request is not None assert args.reason is not None return BoltResponse(status=502, body="customized") oauth_flow = OAuthFlow( client=WebClient(base_url=self.mock_api_server_base_url), settings=OAuthSettings( client_id="111.222", client_secret="xxx", scopes=["chat:write", "commands"], installation_store=FileInstallationStore(), state_store=FileOAuthStateStore(expiration_seconds=120), callback_options=CallbackOptions(success=success, failure=failure), ), ) state = oauth_flow.issue_new_state(None) req = BoltRequest( body="", query=f"code=foo&state={state}", headers={ "cookie": [f"{oauth_flow.settings.state_cookie_name}={state}"] }, ) resp = oauth_flow.handle_callback(req) assert resp.status == 200 assert resp.body == "customized" state = oauth_flow.issue_new_state(None) req = BoltRequest( body="", query=f"code=foo&state=invalid", headers={ "cookie": [f"{oauth_flow.settings.state_cookie_name}={state}"] }, ) resp = oauth_flow.handle_callback(req) assert resp.status == 502 assert resp.body == "customized"
def test_oauth(self): app = App( client=self.web_client, signing_secret=self.signing_secret, oauth_settings=OAuthSettings( client_id="111.111", client_secret="xxx", scopes=["chat:write", "commands"], ), ) api = falcon.API() resource = SlackAppResource(app) api.add_route("/slack/install", resource) client = testing.TestClient(api) response = client.simulate_get("/slack/install") assert response.status_code == 200 assert "https://slack.com/oauth/v2/authorize?state=" in response.text
def test_oauth(self): app = App( client=self.web_client, signing_secret=self.signing_secret, oauth_settings=OAuthSettings( client_id="111.111", client_secret="xxx", scopes=["chat:write", "commands"], ), ) request: Request = testing.DummyRequest() request.path = "/slack/install" request.method = "GET" response: Response = SlackRequestHandler(app).handle(request) assert response.status_code == 200 assert "https://slack.com/oauth/v2/authorize?state=" in response.body.decode( "utf-8")
def test_handle_callback_invalid_state(self): oauth_flow = OAuthFlow( settings=OAuthSettings( client_id="111.222", client_secret="xxx", scopes=["chat:write", "commands"], installation_store=FileInstallationStore(), state_store=FileOAuthStateStore(expiration_seconds=120), ) ) state = oauth_flow.issue_new_state(None) req = BoltRequest( body="", query=f"code=foo&state=invalid", headers={"cookie": [f"{oauth_flow.settings.state_cookie_name}={state}"]}, ) resp = oauth_flow.handle_callback(req) assert resp.status == 400
def test_oauth(self): app = App( client=self.web_client, signing_secret=self.signing_secret, oauth_settings=OAuthSettings( client_id="111.111", client_secret="xxx", scopes=["chat:write", "commands"], ), ) flask_app = Flask(__name__) @flask_app.route("/slack/install", methods=["GET"]) def endpoint(): return SlackRequestHandler(app).handle(request) with flask_app.test_client() as client: rv = client.get("/slack/install") assert rv.status_code == 200
def test_it_redirects(mocker): # given oauth = TeamiclinkOAuth(settings=OAuthSettings( client_id="any_client_id", client_secret="any_client_secret")) issue_state = mocker.patch.object(oauth, "issue_new_state") issue_state.return_value = "any_state" build_url = mocker.patch.object(oauth, "build_authorize_url") build_url.return_value = "https://any_url.com" build_cookie = mocker.patch.object(oauth.settings.state_utils, "build_set_cookie_for_new_state") build_cookie.return_value = "any_cookie" bolt_request = BoltRequest(body="any_body") # when result = oauth.handle_installation(request=bolt_request) # then assert result.status == 302 assert result.headers["location"] == [build_url.return_value] assert result.headers["set-cookie"] == [build_cookie.return_value] issue_state.assert_called_once_with(bolt_request) build_url.assert_called_once_with(issue_state.return_value, bolt_request) build_cookie.assert_called_once_with(issue_state.return_value)
) try: engine.execute("select count(*) from slack_bots") except Exception as e: installation_store.metadata.create_all(engine) oauth_state_store.metadata.create_all(engine) # update ngrok/production links in slash commands and enable events page # Initializes your app with your bot token and signing secret app = App( logger=logger, signing_secret=signing_secret, installation_store=installation_store, oauth_settings=OAuthSettings( client_id=client_id, client_secret=client_secret, state_store=oauth_state_store, ), ) @app.middleware def log_request(logger: logging.Logger, body: dict, next: Callable): logger.debug(body) return next() def extract_subtype(body: dict, context: BoltContext, next: Callable): context["type"] = body.get("event", {}).get("type", None) next()
class TestApp: signing_secret = "secret" valid_token = "xoxb-valid" mock_api_server_base_url = "http://*****:*****@W111> Hi there!", "user": "******", "ts": "1595926230.009600", "team": "T111", "channel": "C111", "event_ts": "1595926230.009600", }, "type": "event_callback", "event_id": "Ev111", "event_time": 1595926230, "authed_users": ["W111"], } @staticmethod def handle_app_mention(body, say: Say, payload, event): assert body["event"] == payload assert payload == event say("What's up?") oauth_settings_bot_only = OAuthSettings( client_id="111.222", client_secret="valid", installation_store=BotOnlyMemoryInstallationStore(), installation_store_bot_only=True, state_store=FileOAuthStateStore(expiration_seconds=120), ) oauth_settings = OAuthSettings( client_id="111.222", client_secret="valid", installation_store=BotOnlyMemoryInstallationStore(), installation_store_bot_only=False, state_store=FileOAuthStateStore(expiration_seconds=120), ) def build_app_mention_request(self): timestamp, body = str(int(time())), json.dumps( self.app_mention_request_body) return BoltRequest(body=body, headers=self.build_headers(timestamp, body)) def test_installation_store_bot_only_default(self): app = App( client=self.web_client, signing_secret=self.signing_secret, installation_store=MemoryInstallationStore(), ) app.event("app_mention")(self.handle_app_mention) response = app.dispatch(self.build_app_mention_request()) assert response.status == 200 assert_auth_test_count(self, 1) sleep(1) # wait a bit after auto ack() assert self.mock_received_requests["/chat.postMessage"] == 1 def test_installation_store_bot_only_false(self): app = App( client=self.web_client, signing_secret=self.signing_secret, installation_store=MemoryInstallationStore(), # the default is False installation_store_bot_only=False, ) app.event("app_mention")(self.handle_app_mention) response = app.dispatch(self.build_app_mention_request()) assert response.status == 200 assert_auth_test_count(self, 1) sleep(1) # wait a bit after auto ack() assert self.mock_received_requests["/chat.postMessage"] == 1 def test_installation_store_bot_only(self): app = App( client=self.web_client, signing_secret=self.signing_secret, installation_store=BotOnlyMemoryInstallationStore(), installation_store_bot_only=True, ) app.event("app_mention")(self.handle_app_mention) response = app.dispatch(self.build_app_mention_request()) assert response.status == 200 assert_auth_test_count(self, 1) sleep(1) # wait a bit after auto ack() assert self.mock_received_requests["/chat.postMessage"] == 1 def test_installation_store_bot_only_oauth_settings(self): app = App( client=self.web_client, signing_secret=self.signing_secret, oauth_settings=self.oauth_settings_bot_only, ) app.event("app_mention")(self.handle_app_mention) response = app.dispatch(self.build_app_mention_request()) assert response.status == 200 assert_auth_test_count(self, 1) sleep(1) # wait a bit after auto ack() assert self.mock_received_requests["/chat.postMessage"] == 1 def test_installation_store_bot_only_oauth_settings_conflicts(self): app = App( client=self.web_client, signing_secret=self.signing_secret, installation_store_bot_only=True, oauth_settings=self.oauth_settings, ) app.event("app_mention")(self.handle_app_mention) response = app.dispatch(self.build_app_mention_request()) assert response.status == 200 assert_auth_test_count(self, 1) sleep(1) # wait a bit after auto ack() assert self.mock_received_requests["/chat.postMessage"] == 1 def test_installation_store_bot_only_oauth_flow(self): app = App( client=self.web_client, signing_secret=self.signing_secret, oauth_flow=OAuthFlow(settings=self.oauth_settings_bot_only), ) app.event("app_mention")(self.handle_app_mention) response = app.dispatch(self.build_app_mention_request()) assert response.status == 200 assert_auth_test_count(self, 1) sleep(1) # wait a bit after auto ack() assert self.mock_received_requests["/chat.postMessage"] == 1 def test_installation_store_bot_only_oauth_flow_conflicts(self): app = App( client=self.web_client, signing_secret=self.signing_secret, installation_store_bot_only=True, oauth_flow=OAuthFlow(settings=self.oauth_settings), ) app.event("app_mention")(self.handle_app_mention) response = app.dispatch(self.build_app_mention_request()) assert response.status == 200 assert_auth_test_count(self, 1) sleep(1) # wait a bit after auto ack() assert self.mock_received_requests["/chat.postMessage"] == 1
from slack_bolt.response import BoltResponse from listeners.listeners import register_listeners dotenv.load_dotenv() logging.basicConfig(level=logging.DEBUG) oauth_settings = OAuthSettings( client_id=os.environ["SLACK_CLIENT_ID"], client_secret=os.environ["SLACK_CLIENT_SECRET"], scopes=[ "channels:history", "commands", "groups:history", "im:history", "mpim:history", "chat:write", ], state_store=FileOAuthStateStore(expiration_seconds=600, base_dir="./data/states"), installation_store=FileInstallationStore(base_dir="./data/installations"), ) app = App(signing_secret=os.environ["SLACK_SIGNING_SECRET"], oauth_settings=oauth_settings) register_listeners(app) podthai = [ "UJM7Z5VGD", "U014KC3E9MF", "U0145C1684V", "U014CUFPNJG", "U01401MQJAJ" ]