def test_is_valid_request_body_as_bytes(self): verifier = SignatureVerifier( signing_secret=self.signing_secret, clock=MockClock() ) self.assertTrue( verifier.is_valid_request(self.body.encode("utf-8"), self.headers) )
def test_is_valid_request_invalid_body(self): verifier = SignatureVerifier( signing_secret=self.signing_secret, clock=MockClock(), ) modified_body = self.body + "------" self.assertFalse(verifier.is_valid_request(modified_body, self.headers))
async def verify_signature(request: Request) -> bool: # リクエストの署名を検証 # ref: https://api.slack.com/authentication/verifying-requests-from-slack verifier = SignatureVerifier(settings.SLACK_SIGNING_SECRET) if verifier.is_valid_request(await request.body(), dict(request.headers)): return True raise HTTPException(HTTPStatus.FORBIDDEN)
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 __init__(self, signing_secret: str): """Verifies an incoming request by checking the validity of x-slack-signature, x-slack-request-timestamp, and its body data. :param signing_secret: The signing secret. """ self.verifier = SignatureVerifier(signing_secret=signing_secret) self.logger = get_bolt_logger(RequestVerification)
def test_is_valid_request_none(self): verifier = SignatureVerifier( signing_secret=self.signing_secret, clock=MockClock(), ) self.assertFalse(verifier.is_valid_request(None, self.headers)) self.assertFalse(verifier.is_valid_request(self.body, None)) self.assertFalse(verifier.is_valid_request(None, None))
def test_is_valid(self): verifier = SignatureVerifier( signing_secret=self.signing_secret, clock=MockClock(), ) self.assertTrue( verifier.is_valid(self.body, self.timestamp, self.valid_signature) ) self.assertTrue(verifier.is_valid(self.body, 1531420618, self.valid_signature))
def __init__(self, signing_secret: str): """Verifies an incoming request by checking the validity of `x-slack-signature`, `x-slack-request-timestamp`, and its body data. Refer to https://api.slack.com/authentication/verifying-requests-from-slack for details. Args: signing_secret: The signing secret """ self.verifier = SignatureVerifier(signing_secret=signing_secret) self.logger = get_bolt_logger(RequestVerification)
def wrapper_verify_slack_signature(request, *args, **kwargs): signature_verifier = SignatureVerifier( signing_secret=settings.SLACK_SIGNING_SECRET) if not signature_verifier.is_valid( body=request.body, timestamp=request.headers.get("X-Slack-Request-Timestamp"), signature=request.headers.get("X-Slack-Signature"), ): return HttpResponseForbidden() return func(request, *args, **kwargs)
def test_verified(): app.dependency_overrides[verify_signature] = verify_signature client = TestClient(app) timestamp = int(datetime.now().timestamp()) data = {'type': 'url_verification', 'token': 'token'} verifier = SignatureVerifier(settings.SLACK_SIGNING_SECRET) signature = verifier.generate_signature(timestamp=timestamp, body=json.dumps(data)) headers = { 'x-slack-request-timestamp': str(timestamp), 'x-slack-signature': signature } res = client.post('/v1/events/', json=data, headers=headers) assert res.status_code == HTTPStatus.OK
def test_is_valid_none(self): verifier = SignatureVerifier( signing_secret=self.signing_secret, clock=MockClock(), ) self.assertFalse(verifier.is_valid(None, self.timestamp, self.valid_signature)) self.assertFalse(verifier.is_valid(self.body, None, self.valid_signature)) self.assertFalse(verifier.is_valid(self.body, self.timestamp, None)) self.assertFalse(verifier.is_valid(None, None, self.valid_signature)) self.assertFalse(verifier.is_valid(None, self.timestamp, None)) self.assertFalse(verifier.is_valid(self.body, None, None)) self.assertFalse(verifier.is_valid(None, None, None))
def execute(self, raw_body, headers): is_valid = SignatureVerifier(self.signing_secret).is_valid_request( headers=headers, body=raw_body ) if not is_valid: logging.warning(f"SignatureVerifier returned {is_valid}") return is_valid
class TestRequestVerification: signing_secret = "secret" signature_verifier = SignatureVerifier(signing_secret) def generate_signature(self, body: str, timestamp: str): return self.signature_verifier.generate_signature( body=body, timestamp=timestamp, ) def build_headers(self, timestamp: str, body: str): return { "content-type": ["application/x-www-form-urlencoded"], "x-slack-signature": [self.generate_signature(body, timestamp)], "x-slack-request-timestamp": [timestamp], } def test_valid(self): middleware = RequestVerification(signing_secret=self.signing_secret) timestamp = str(int(time())) raw_body = "payload={}" req = BoltRequest(body=raw_body, headers=self.build_headers(timestamp, raw_body)) resp = BoltResponse(status=404, body="default") resp = middleware.process(req=req, resp=resp, next=next) assert resp.status == 200 assert resp.body == "next" def test_invalid(self): middleware = RequestVerification(signing_secret=self.signing_secret) req = BoltRequest(body="payload={}", headers={}) resp = BoltResponse(status=404) resp = middleware.process(req=req, resp=resp, next=next) assert resp.status == 401 assert resp.body == """{"error": "invalid request"}"""
def event_signature_headers(secret, data): timestamp = str(int(time())) return { "X-Slack-Request-Timestamp": str(int(timestamp)), "X-Slack-Signature": SignatureVerifier(secret).generate_signature( timestamp=timestamp, body=data ), }
class TestAsyncInstallationStoreAuthorize: signing_secret = "secret" mock_api_server_base_url = "http://localhost:8888" signature_verifier = SignatureVerifier(signing_secret) web_client = AsyncWebClient( token=valid_token, base_url=mock_api_server_base_url, ) @pytest.fixture def event_loop(self): old_os_env = remove_os_env_temporarily() try: setup_mock_web_api_server(self) loop = asyncio.get_event_loop() yield loop loop.close() cleanup_mock_web_api_server(self) finally: restore_os_env(old_os_env) def generate_signature(self, body: str, timestamp: str): return self.signature_verifier.generate_signature( body=body, timestamp=timestamp, ) def build_headers(self, timestamp: str, body: str): return { "content-type": ["application/x-www-form-urlencoded"], "x-slack-signature": [self.generate_signature(body, timestamp)], "x-slack-request-timestamp": [timestamp], } def build_valid_request(self) -> AsyncBoltRequest: timestamp = str(int(time())) return AsyncBoltRequest(body=raw_body, headers=self.build_headers( timestamp, raw_body)) @pytest.mark.asyncio async def test_success(self): app = AsyncApp( client=self.web_client, installation_store=MyInstallationStore(), signing_secret=self.signing_secret, ) app.action("a")(simple_listener) request = self.build_valid_request() response = await app.async_dispatch(request) assert response.status == 200 assert response.body == "" await assert_auth_test_count_async(self, 1)
class TestAsyncSSLCheck: signing_secret = "secret" valid_token = "xoxb-valid" mock_api_server_base_url = "http://localhost:8888" signature_verifier = SignatureVerifier(signing_secret) web_client = AsyncWebClient( token=valid_token, base_url=mock_api_server_base_url, ) @pytest.fixture def event_loop(self): old_os_env = remove_os_env_temporarily() try: setup_mock_web_api_server(self) loop = asyncio.get_event_loop() yield loop loop.close() cleanup_mock_web_api_server(self) finally: restore_os_env(old_os_env) def generate_signature(self, body: str, timestamp: str): return self.signature_verifier.generate_signature( body=body, timestamp=timestamp, ) @pytest.mark.asyncio async def test_mock_server_is_running(self): resp = await self.web_client.api_test() assert resp != None @pytest.mark.asyncio async def test_ssl_check(self): app = AsyncApp(client=self.web_client, signing_secret=self.signing_secret) timestamp, body = str(int(time())), "token=random&ssl_check=1" request: AsyncBoltRequest = AsyncBoltRequest( body=body, query={}, headers={ "content-type": ["application/x-www-form-urlencoded"], "x-slack-signature": [self.generate_signature(body, timestamp)], "x-slack-request-timestamp": [timestamp], }, ) response = await app.async_dispatch(request) assert response.status == 200 assert response.body == ""
class TestInstallationStoreAuthorize: signing_secret = "secret" mock_api_server_base_url = "http://localhost:8888" signature_verifier = SignatureVerifier(signing_secret) web_client = WebClient( token=valid_token, base_url=mock_api_server_base_url, ) def setup_method(self): self.old_os_env = remove_os_env_temporarily() setup_mock_web_api_server(self) def teardown_method(self): cleanup_mock_web_api_server(self) restore_os_env(self.old_os_env) def generate_signature(self, body: str, timestamp: str): return self.signature_verifier.generate_signature( body=body, timestamp=timestamp, ) def build_headers(self, timestamp: str, body: str): return { "content-type": ["application/x-www-form-urlencoded"], "x-slack-signature": [self.generate_signature(body, timestamp)], "x-slack-request-timestamp": [timestamp], } def build_valid_request(self) -> BoltRequest: timestamp = str(int(time())) return BoltRequest( body=raw_body, headers=self.build_headers(timestamp, raw_body) ) def test_success(self): app = App( client=self.web_client, installation_store=MyInstallationStore(), signing_secret=self.signing_secret, ) app.action("a")(simple_listener) request = self.build_valid_request() response = app.dispatch(request) assert response.status == 200 assert response.body == "" assert_auth_test_count(self, 1)
class RequestVerification(Middleware): # type: ignore def __init__(self, signing_secret: str): """Verifies an incoming request by checking the validity of `x-slack-signature`, `x-slack-request-timestamp`, and its body data. Refer to https://api.slack.com/authentication/verifying-requests-from-slack for details. Args: signing_secret: The signing secret """ self.verifier = SignatureVerifier(signing_secret=signing_secret) self.logger = get_bolt_logger(RequestVerification) def process( self, *, req: BoltRequest, resp: BoltResponse, next: Callable[[], BoltResponse], ) -> BoltResponse: if self._can_skip(req.mode, req.body): return next() body = req.raw_body timestamp = req.headers.get("x-slack-request-timestamp", ["0"])[0] signature = req.headers.get("x-slack-signature", [""])[0] if self.verifier.is_valid(body, timestamp, signature): return next() else: self._debug_log_error(signature, timestamp, body) return self._build_error_response() # ----------------------------------------- @staticmethod def _can_skip(mode: str, body: Dict[str, Any]) -> bool: return mode == "socket_mode" or (body is not None and body.get("ssl_check") == "1") @staticmethod def _build_error_response() -> BoltResponse: return BoltResponse(status=401, body={"error": "invalid request"}) def _debug_log_error(self, signature, timestamp, body) -> None: self.logger.info( "Invalid request signature detected " f"(signature: {signature}, timestamp: {timestamp}, body: {body})")
class TestSSLCheck: signing_secret = "secret" valid_token = "xoxb-valid" mock_api_server_base_url = "http://localhost:8888" signature_verifier = SignatureVerifier(signing_secret) web_client = WebClient( token=valid_token, base_url=mock_api_server_base_url, ) def setup_method(self): self.old_os_env = remove_os_env_temporarily() setup_mock_web_api_server(self) def teardown_method(self): cleanup_mock_web_api_server(self) restore_os_env(self.old_os_env) def generate_signature(self, body: str, timestamp: str): return self.signature_verifier.generate_signature( body=body, timestamp=timestamp, ) def test_mock_server_is_running(self): resp = self.web_client.api_test() assert resp != None def test_ssl_check(self): app = App(client=self.web_client, signing_secret=self.signing_secret) timestamp, body = str(int(time())), "token=random&ssl_check=1" request: BoltRequest = BoltRequest( body=body, query={}, headers={ "content-type": ["application/x-www-form-urlencoded"], "x-slack-signature": [self.generate_signature(body, timestamp)], "x-slack-request-timestamp": [timestamp], }, ) response = app.dispatch(request) assert response.status == 200 assert response.body == ""
class TestCherryPy(helper.CPWebCase): helper.CPWebCase.interactive = False signing_secret = "secret" signature_verifier = SignatureVerifier(signing_secret) @classmethod 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") @classmethod def teardown_class(cls): cls.supervisor.stop() restore_os_env(cls.old_os_env) def test_oauth(self): cherrypy.request.process_request_body = False self.getPage("/slack/install", method="GET") self.assertStatus("200 OK")
class TestAsyncRequestVerification: signing_secret = "secret" signature_verifier = SignatureVerifier(signing_secret) def generate_signature(self, body: str, timestamp: str): return self.signature_verifier.generate_signature( body=body, timestamp=timestamp, ) def build_headers(self, timestamp: str, body: str): return { "content-type": ["application/x-www-form-urlencoded"], "x-slack-signature": [self.generate_signature(body, timestamp)], "x-slack-request-timestamp": [timestamp], } @pytest.fixture def event_loop(self): loop = asyncio.get_event_loop() yield loop loop.close() @pytest.mark.asyncio async def test_valid(self): middleware = AsyncRequestVerification(signing_secret="secret") timestamp = str(int(time())) raw_body = "payload={}" req = AsyncBoltRequest(body=raw_body, headers=self.build_headers(timestamp, raw_body)) resp = BoltResponse(status=404) resp = await middleware.async_process(req=req, resp=resp, next=next) assert resp.status == 200 assert resp.body == "next" @pytest.mark.asyncio async def test_invalid(self): middleware = AsyncRequestVerification(signing_secret="secret") req = AsyncBoltRequest(body="payload={}", headers={}) resp = BoltResponse(status=404) resp = await middleware.async_process(req=req, resp=resp, next=next) assert resp.status == 401 assert resp.body == """{"error": "invalid request"}"""
class RequestVerification(Middleware): # type: ignore def __init__(self, signing_secret: str): self.verifier = SignatureVerifier(signing_secret=signing_secret) self.logger = get_bolt_logger(RequestVerification) def process( self, *, req: BoltRequest, resp: BoltResponse, next: Callable[[], BoltResponse], ) -> BoltResponse: if self._can_skip(req.payload): return next() body = req.body timestamp = req.headers.get("x-slack-request-timestamp", ["0"])[0] signature = req.headers.get("x-slack-signature", [""])[0] if self.verifier.is_valid(body, timestamp, signature): return next() else: self._debug_log_error(signature, timestamp, body) return self._build_error_response() # ----------------------------------------- @staticmethod def _can_skip(payload: Dict[str, Any]) -> bool: return payload is not None and payload.get("ssl_check", None) == "1" @staticmethod def _build_error_response() -> BoltResponse: return BoltResponse(status=401, body={"error": "invalid request"}) def _debug_log_error(self, signature, timestamp, body) -> None: self.logger.info( "Invalid request signature detected " f"(signature: {signature}, timestamp: {timestamp}, body: {body})")
class TestEventsSharedChannels: signing_secret = "secret" mock_api_server_base_url = "http://*****:*****@W111> Hi there!", "user": "******", "ts": "1595926230.009600", "team": "T_INSTALLED", "channel": "C111", "event_ts": "1595926230.009600", }, "type": "event_callback", "event_id": "Ev111", "event_time": 1595926230, "authorizations": [ { "enterprise_id": "E_INSTALLED", "team_id": "T_INSTALLED", "user_id": "W111", "is_bot": True, "is_enterprise_install": False, } ], } def test_mock_server_is_running(self): resp = self.web_client.api_test(token=valid_token) assert resp != None def test_middleware(self): app = App( client=self.web_client, signing_secret=self.signing_secret, authorize=authorize, ) @app.event("app_mention") def handle_app_mention(body, say: Say, payload, event): assert body == self.valid_event_body assert body["event"] == payload assert payload == event say("What's up?") timestamp, body = str(int(time())), json.dumps(self.valid_event_body) request: BoltRequest = BoltRequest( body=body, headers=self.build_headers(timestamp, body) ) response = app.dispatch(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_middleware_skip(self): app = App( client=self.web_client, signing_secret=self.signing_secret, authorize=authorize, ) def skip_middleware(req, resp, next): # return next() pass @app.event("app_mention", middleware=[skip_middleware]) def handle_app_mention(body, logger, payload, event): assert body["event"] == payload assert payload == event logger.info(payload) timestamp, body = str(int(time())), json.dumps(self.valid_event_body) request: BoltRequest = BoltRequest( body=body, headers=self.build_headers(timestamp, body) ) response = app.dispatch(request) assert response.status == 404 assert_auth_test_count(self, 1) valid_reaction_added_body = { "token": "verification_token", "team_id": "T_SOURCE", "enterprise_id": "E_SOURCE", "api_app_id": "A111", "event": { "type": "reaction_added", "user": "******", "item": {"type": "message", "channel": "C111", "ts": "1599529504.000400"}, "reaction": "heart_eyes", "item_user": "******", "event_ts": "1599616881.000800", }, "type": "event_callback", "event_id": "Ev111", "event_time": 1599616881, "authorizations": [ { "enterprise_id": "E_INSTALLED", "team_id": "T_INSTALLED", "user_id": "W111", "is_bot": True, "is_enterprise_install": False, } ], } def test_reaction_added(self): app = App( client=self.web_client, signing_secret=self.signing_secret, authorize=authorize, ) @app.event("reaction_added") def handle_app_mention(body, say, payload, event): assert body == self.valid_reaction_added_body assert body["event"] == payload assert payload == event say("What's up?") timestamp, body = str(int(time())), json.dumps(self.valid_reaction_added_body) request: BoltRequest = BoltRequest( body=body, headers=self.build_headers(timestamp, body) ) response = app.dispatch(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_stable_auto_ack(self): app = App( client=self.web_client, signing_secret=self.signing_secret, authorize=authorize, ) @app.event("reaction_added") def handle_app_mention(): raise Exception("Something wrong!") for _ in range(10): timestamp, body = ( str(int(time())), json.dumps(self.valid_reaction_added_body), ) request: BoltRequest = BoltRequest( body=body, headers=self.build_headers(timestamp, body) ) response = app.dispatch(request) assert response.status == 200 def test_self_events(self): app = App( client=self.web_client, signing_secret=self.signing_secret, authorize=authorize, ) event_body = { "token": "verification_token", "team_id": "T_SOURCE", "enterprise_id": "E_SOURCE", "api_app_id": "A111", "event": { "type": "reaction_added", "user": "******", # bot_user_id "item": { "type": "message", "channel": "C111", "ts": "1599529504.000400", }, "reaction": "heart_eyes", "item_user": "******", "event_ts": "1599616881.000800", }, "type": "event_callback", "event_id": "Ev111", "event_time": 1599616881, "authorizations": [ { "enterprise_id": "E_INSTALLED", "team_id": "T_INSTALLED", "user_id": "W111", "is_bot": True, "is_enterprise_install": False, } ], } @app.event("reaction_added") def handle_app_mention(say): say("What's up?") timestamp, body = str(int(time())), json.dumps(event_body) request: BoltRequest = BoltRequest( body=body, headers=self.build_headers(timestamp, body) ) response = app.dispatch(request) assert response.status == 200 assert_auth_test_count(self, 1) sleep(1) # wait a bit after auto ack() # The listener should not be executed assert self.mock_received_requests.get("/chat.postMessage") is None def test_self_member_join_left_events(self): app = App( client=self.web_client, signing_secret=self.signing_secret, authorize=authorize, ) join_event_body = { "token": "verification_token", "team_id": "T_SOURCE", "enterprise_id": "E_SOURCE", "api_app_id": "A111", "event": { "type": "member_joined_channel", "user": "******", # bot_user_id "channel": "C111", "channel_type": "C", "team": "T_INSTALLED", "inviter": "U222", }, "type": "event_callback", "event_id": "Ev111", "event_time": 1599616881, "authorizations": [ { "enterprise_id": "E_INSTALLED", "team_id": "T_INSTALLED", "user_id": "W111", "is_bot": True, "is_enterprise_install": False, } ], } left_event_body = { "token": "verification_token", "team_id": "T_SOURCE", "enterprise_id": "E_SOURCE", "api_app_id": "A111", "event": { "type": "member_left_channel", "user": "******", # bot_user_id "channel": "C111", "channel_type": "C", "team": "T_INSTALLED", }, "type": "event_callback", "event_id": "Ev111", "event_time": 1599616881, "authorizations": [ { "enterprise_id": "E_INSTALLED", "team_id": "T_INSTALLED", "user_id": "W111", "is_bot": True, "is_enterprise_install": False, } ], } @app.event("member_joined_channel") def handle_member_joined_channel(say): say("What's up?") @app.event("member_left_channel") def handle_member_left_channel(say): say("What's up?") timestamp, body = str(int(time())), json.dumps(join_event_body) request: BoltRequest = BoltRequest( body=body, headers=self.build_headers(timestamp, body) ) response = app.dispatch(request) assert response.status == 200 assert_auth_test_count(self, 1) timestamp, body = str(int(time())), json.dumps(left_event_body) request: BoltRequest = BoltRequest( body=body, headers=self.build_headers(timestamp, body) ) response = app.dispatch(request) assert response.status == 200 sleep(1) # wait a bit after auto ack() assert self.mock_received_requests["/chat.postMessage"] == 2 def test_member_join_left_events(self): app = App( client=self.web_client, signing_secret=self.signing_secret, authorize=authorize, ) join_event_body = { "token": "verification_token", "team_id": "T_SOURCE", "enterprise_id": "E_SOURCE", "api_app_id": "A111", "event": { "type": "member_joined_channel", "user": "******", # not self "channel": "C111", "channel_type": "C", "team": "T_INSTALLED", "inviter": "U222", }, "type": "event_callback", "event_id": "Ev111", "event_time": 1599616881, "authorizations": [ { "enterprise_id": "E_INSTALLED", "team_id": "T_INSTALLED", "user_id": "W111", "is_bot": True, "is_enterprise_install": False, } ], } left_event_body = { "token": "verification_token", "team_id": "T_SOURCE", "enterprise_id": "E_SOURCE", "api_app_id": "A111", "event": { "type": "member_left_channel", "user": "******", # not self "channel": "C111", "channel_type": "C", "team": "T_INSTALLED", }, "type": "event_callback", "event_id": "Ev111", "event_time": 1599616881, "authorizations": [ { "enterprise_id": "E_INSTALLED", "team_id": "T_INSTALLED", "user_id": "W111", "is_bot": True, "is_enterprise_install": False, } ], } @app.event("member_joined_channel") def handle_app_mention(say): say("What's up?") @app.event("member_left_channel") def handle_app_mention(say): say("What's up?") timestamp, body = str(int(time())), json.dumps(join_event_body) request: BoltRequest = BoltRequest( body=body, headers=self.build_headers(timestamp, body) ) response = app.dispatch(request) assert response.status == 200 assert_auth_test_count(self, 1) timestamp, body = str(int(time())), json.dumps(left_event_body) request: BoltRequest = BoltRequest( body=body, headers=self.build_headers(timestamp, body) ) response = app.dispatch(request) assert response.status == 200 sleep(1) # wait a bit after auto ack() # the listeners should not be executed assert self.mock_received_requests["/chat.postMessage"] == 2 def test_uninstallation_and_revokes(self): app = App( client=self.web_client, signing_secret=self.signing_secret, authorize=authorize, ) app._client = WebClient( token="uninstalled-revoked", base_url=self.mock_api_server_base_url ) @app.event("app_uninstalled") def handler1(say: Say): say(channel="C111", text="What's up?") @app.event("tokens_revoked") def handler2(say: Say): say(channel="C111", text="What's up?") app_uninstalled_body = { "token": "verification_token", "team_id": "T_INSTALLED", "enterprise_id": "E_INSTALLED", "api_app_id": "A111", "event": {"type": "app_uninstalled"}, "type": "event_callback", "event_id": "Ev111", "event_time": 1599616881, "authorizations": [ { "enterprise_id": "E_INSTALLED", "team_id": "T_INSTALLED", "user_id": "W111", "is_bot": True, "is_enterprise_install": False, } ], } timestamp, body = str(int(time())), json.dumps(app_uninstalled_body) request: BoltRequest = BoltRequest( body=body, headers=self.build_headers(timestamp, body) ) response = app.dispatch(request) assert response.status == 200 tokens_revoked_body = { "token": "verification_token", "team_id": "T_INSTALLED", "enterprise_id": "E_INSTALLED", "api_app_id": "A111", "event": { "type": "tokens_revoked", "tokens": {"oauth": ["UXXXXXXXX"], "bot": ["UXXXXXXXX"]}, }, "type": "event_callback", "event_id": "Ev111", "event_time": 1599616881, "authorizations": [ { "enterprise_id": "E_INSTALLED", "team_id": "T_INSTALLED", "user_id": "W111", "is_bot": True, "is_enterprise_install": False, } ], } timestamp, body = str(int(time())), json.dumps(tokens_revoked_body) request: BoltRequest = BoltRequest( body=body, headers=self.build_headers(timestamp, body) ) response = app.dispatch(request) assert response.status == 200 # this should not be called when we have authorize assert self.mock_received_requests.get("/auth.test") is None sleep(1) # wait a bit after auto ack() assert self.mock_received_requests["/chat.postMessage"] == 2
class TestAsyncOrgApps: signing_secret = "secret" valid_token = "xoxb-valid" mock_api_server_base_url = "http://localhost:8888" signature_verifier = SignatureVerifier(signing_secret) web_client = AsyncWebClient(token=None, base_url=mock_api_server_base_url) @pytest.fixture def event_loop(self): old_os_env = remove_os_env_temporarily() try: setup_mock_web_api_server(self) loop = asyncio.get_event_loop() yield loop loop.close() cleanup_mock_web_api_server(self) finally: restore_os_env(old_os_env) def generate_signature(self, body: str, timestamp: str): return self.signature_verifier.generate_signature( body=body, timestamp=timestamp, ) def build_headers(self, timestamp: str, body: str): return { "content-type": ["application/json"], "x-slack-signature": [self.generate_signature(body, timestamp)], "x-slack-request-timestamp": [timestamp], } @pytest.mark.asyncio async def test_team_access_granted(self): app = AsyncApp( client=self.web_client, signing_secret=self.signing_secret, installation_store=OrgAppInstallationStore(), ) event_payload = { "token": "verification-token", "enterprise_id": "E111", "api_app_id": "A111", "event": { "type": "team_access_granted", "team_ids": ["T111", "T222"], "event_ts": "111.222", }, "type": "event_callback", "event_id": "Ev111", "event_time": 1606805974, } result = Result() @app.event("team_access_granted") async def handle_app_mention(body): assert body == event_payload result.called = True timestamp, body = str(int(time())), json.dumps(event_payload) request: AsyncBoltRequest = AsyncBoltRequest( body=body, headers=self.build_headers(timestamp, body)) response = await app.async_dispatch(request) assert response.status == 200 # auth.test API call must be skipped assert self.mock_received_requests["/auth.test"] == 1 await asyncio.sleep(1) # wait a bit after auto ack() assert result.called is True @pytest.mark.asyncio async def test_team_access_revoked(self): app = AsyncApp( client=self.web_client, signing_secret=self.signing_secret, installation_store=OrgAppInstallationStore(), ) event_payload = { "token": "verification-token", "enterprise_id": "E111", "api_app_id": "A111", "event": { "type": "team_access_revoked", "team_ids": ["T111", "T222"], "event_ts": "1606805732.987656", }, "type": "event_callback", "event_id": "Ev111", "event_time": 1606805732, } result = Result() @app.event("team_access_revoked") async def handle_app_mention(body): assert body == event_payload result.called = True timestamp, body = str(int(time())), json.dumps(event_payload) request: AsyncBoltRequest = AsyncBoltRequest( body=body, headers=self.build_headers(timestamp, body)) response = await app.async_dispatch(request) assert response.status == 200 # auth.test API call must be skipped assert self.mock_received_requests.get("/auth.test") is None await asyncio.sleep(1) # wait a bit after auto ack() assert result.called is True @pytest.mark.asyncio async def test_app_home_opened(self): app = AsyncApp( client=self.web_client, signing_secret=self.signing_secret, installation_store=OrgAppInstallationStore(), ) event_payload = { "token": "verification-token", "team_id": "T111", "enterprise_id": "E111", "api_app_id": "A111", "event": { "type": "app_home_opened", "user": "******", "channel": "D111", "tab": "messages", "event_ts": "1606810927.510671", }, "type": "event_callback", "event_id": "Ev111", "event_time": 1606810927, "authorizations": [{ "enterprise_id": "E111", "team_id": None, "user_id": "W111", "is_bot": True, "is_enterprise_install": True, }], "is_ext_shared_channel": False, } result = Result() @app.event("app_home_opened") async def handle_app_mention(body): assert body == event_payload result.called = True timestamp, body = str(int(time())), json.dumps(event_payload) request: AsyncBoltRequest = AsyncBoltRequest( body=body, headers=self.build_headers(timestamp, body)) response = await app.async_dispatch(request) assert response.status == 200 # auth.test API call must be skipped assert self.mock_received_requests["/auth.test"] == 1 await asyncio.sleep(1) # wait a bit after auto ack() assert result.called is True @pytest.mark.asyncio async def test_message(self): app = AsyncApp( client=self.web_client, signing_secret=self.signing_secret, installation_store=OrgAppInstallationStore(), ) event_payload = { "token": "verification-token", "team_id": "T111", "enterprise_id": "E111", "api_app_id": "A111", "event": { "client_msg_id": "0186b75a-2ad4-4f36-8ccc-18608b0ac5d1", "type": "message", "text": "<@W222>", "user": "******", "ts": "1606810819.000800", "team": "T111", "channel": "C111", "event_ts": "1606810819.000800", "channel_type": "channel", }, "type": "event_callback", "event_id": "Ev111", "event_time": 1606810819, "authed_users": [], "authorizations": [{ "enterprise_id": "E111", "team_id": None, "user_id": "W222", "is_bot": True, "is_enterprise_install": True, }], "is_ext_shared_channel": False, "event_context": "1-message-T111-C111", } result = Result() @app.event("message") async def handle_app_mention(body): assert body == event_payload result.called = True timestamp, body = str(int(time())), json.dumps(event_payload) request: AsyncBoltRequest = AsyncBoltRequest( body=body, headers=self.build_headers(timestamp, body)) response = await app.async_dispatch(request) assert response.status == 200 # auth.test API call must be skipped assert self.mock_received_requests["/auth.test"] == 1 await asyncio.sleep(1) # wait a bit after auto ack() assert result.called is True
class TestCherryPy(helper.CPWebCase): helper.CPWebCase.interactive = False signing_secret = "secret" signature_verifier = SignatureVerifier(signing_secret) @classmethod def setup_server(cls): cls.old_os_env = remove_os_env_temporarily() setup_mock_web_api_server(cls) 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,) def event_handler(): pass def shortcut_handler(ack): ack() def command_handler(ack): ack() app.event("app_mention")(event_handler) app.shortcut("test-shortcut")(shortcut_handler) app.command("/hello-world")(command_handler) handler = SlackRequestHandler(app) class SlackApp(object): @cherrypy.expose @cherrypy.tools.slack_in() def events(self, **kwargs): return handler.handle() cherrypy.tree.mount(SlackApp(), "/slack") @classmethod def teardown_class(cls): cls.supervisor.stop() cleanup_mock_web_api_server(cls) restore_os_env(cls.old_os_env) def generate_signature(self, body: str, timestamp: str): return self.signature_verifier.generate_signature( body=body, timestamp=timestamp, ) def build_headers(self, timestamp: str, body: str): return [ ("content-length", str(len(body))), ("x-slack-signature", self.generate_signature(body, timestamp)), ("x-slack-request-timestamp", timestamp), ] def test_events(self): input = { "token": "verification_token", "team_id": "T111", "enterprise_id": "E111", "api_app_id": "A111", "event": { "client_msg_id": "9cbd4c5b-7ddf-4ede-b479-ad21fca66d63", "type": "app_mention", "text": "<@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"], } timestamp, body = str(int(time())), json.dumps(input) cherrypy.request.process_request_body = True self.getPage( "/slack/events", method="POST", body=body, headers=self.build_headers(timestamp, body), ) self.assertStatus("200 OK") self.assertBody("") def test_shortcuts(self): input = { "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", } timestamp, body = str(int(time())), f"payload={quote(json.dumps(input))}" cherrypy.request.process_request_body = True self.getPage( "/slack/events", method="POST", body=body, headers=self.build_headers(timestamp, body), ) self.assertStatus("200 OK") self.assertBody("") def test_commands(self): input = ( "token=verification_token" "&team_id=T111" "&team_domain=test-domain" "&channel_id=C111" "&channel_name=random" "&user_id=W111" "&user_name=primary-owner" "&command=%2Fhello-world" "&text=Hi" "&enterprise_id=E111" "&enterprise_name=Org+Name" "&response_url=https%3A%2F%2Fhooks.slack.com%2Fcommands%2FT111%2F111%2Fxxxxx" "&trigger_id=111.111.xxx" ) timestamp, body = str(int(time())), input cherrypy.request.process_request_body = True self.getPage( "/slack/events", method="POST", body=body, headers=self.build_headers(timestamp, body), ) self.assertStatus("200 OK") self.assertBody("")
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
class TestAWSLambda: signing_secret = "secret" valid_token = "xoxb-valid" mock_api_server_base_url = "http://localhost:8888" signature_verifier = SignatureVerifier(signing_secret) web_client = WebClient( token=valid_token, base_url=mock_api_server_base_url, ) context = LambdaContext(function_name="test-function") def setup_method(self): self.old_os_env = remove_os_env_temporarily() setup_mock_web_api_server(self) def teardown_method(self): cleanup_mock_web_api_server(self) restore_os_env(self.old_os_env) def generate_signature(self, body: str, timestamp: str): return self.signature_verifier.generate_signature( body=body, timestamp=timestamp, ) def build_headers(self, timestamp: str, body: str): content_type = ("application/json" if body.startswith("{") else "application/x-www-form-urlencoded") return { "content-type": [content_type], "x-slack-signature": [self.generate_signature(body, timestamp)], "x-slack-request-timestamp": [timestamp], } def test_not_found(self): response = not_found() assert response["statusCode"] == 404 def test_first_value(self): assert _first_value({"foo": [1, 2, 3]}, "foo") == 1 assert _first_value({"foo": []}, "foo") is None assert _first_value({}, "foo") is None @mock_lambda def test_clear_all_log_handlers(self): app = App( client=self.web_client, signing_secret=self.signing_secret, ) handler = SlackRequestHandler(app) handler.clear_all_log_handlers() @mock_lambda def test_events(self): app = App( client=self.web_client, signing_secret=self.signing_secret, ) def event_handler(): pass app.event("app_mention")(event_handler) input = { "token": "verification_token", "team_id": "T111", "enterprise_id": "E111", "api_app_id": "A111", "event": { "client_msg_id": "9cbd4c5b-7ddf-4ede-b479-ad21fca66d63", "type": "app_mention", "text": "<@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"], } timestamp, body = str(int(time())), json.dumps(input) event = { "body": body, "queryStringParameters": {}, "headers": self.build_headers(timestamp, body), "requestContext": { "http": { "method": "POST" } }, "isBase64Encoded": False, } response = SlackRequestHandler(app).handle(event, self.context) assert response["statusCode"] == 200 assert self.mock_received_requests["/auth.test"] == 1 event = { "body": body, "queryStringParameters": {}, "headers": self.build_headers(timestamp, body), "requestContext": { "httpMethod": "POST" }, "isBase64Encoded": False, } response = SlackRequestHandler(app).handle(event, self.context) assert response["statusCode"] == 200 assert self.mock_received_requests["/auth.test"] == 1 @mock_lambda def test_shortcuts(self): app = App( client=self.web_client, signing_secret=self.signing_secret, ) def shortcut_handler(ack): ack() app.shortcut("test-shortcut")(shortcut_handler) input = { "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", } timestamp, body = str(int( time())), f"payload={quote(json.dumps(input))}" event = { "body": body, "queryStringParameters": {}, "headers": self.build_headers(timestamp, body), "requestContext": { "http": { "method": "POST" } }, "isBase64Encoded": False, } response = SlackRequestHandler(app).handle(event, self.context) assert response["statusCode"] == 200 assert self.mock_received_requests["/auth.test"] == 1 event = { "body": body, "queryStringParameters": {}, "headers": self.build_headers(timestamp, body), "requestContext": { "httpMethod": "POST" }, "isBase64Encoded": False, } response = SlackRequestHandler(app).handle(event, self.context) assert response["statusCode"] == 200 assert self.mock_received_requests["/auth.test"] == 1 @mock_lambda def test_commands(self): app = App( client=self.web_client, signing_secret=self.signing_secret, ) def command_handler(ack): ack() app.command("/hello-world")(command_handler) input = ( "token=verification_token" "&team_id=T111" "&team_domain=test-domain" "&channel_id=C111" "&channel_name=random" "&user_id=W111" "&user_name=primary-owner" "&command=%2Fhello-world" "&text=Hi" "&enterprise_id=E111" "&enterprise_name=Org+Name" "&response_url=https%3A%2F%2Fhooks.slack.com%2Fcommands%2FT111%2F111%2Fxxxxx" "&trigger_id=111.111.xxx") timestamp, body = str(int(time())), input event = { "body": body, "queryStringParameters": {}, "headers": self.build_headers(timestamp, body), "requestContext": { "http": { "method": "POST" } }, "isBase64Encoded": False, } response = SlackRequestHandler(app).handle(event, self.context) assert response["statusCode"] == 200 assert self.mock_received_requests["/auth.test"] == 1 event = { "body": body, "queryStringParameters": {}, "headers": self.build_headers(timestamp, body), "requestContext": { "httpMethod": "POST" }, "isBase64Encoded": False, } response = SlackRequestHandler(app).handle(event, self.context) assert response["statusCode"] == 200 assert self.mock_received_requests["/auth.test"] == 1 @mock_lambda def test_lazy_listeners(self): app = App( client=self.web_client, signing_secret=self.signing_secret, ) def command_handler(ack): ack() def say_it(say): say("Done!") app.command("/hello-world")(ack=command_handler, lazy=[say_it]) input = ( "token=verification_token" "&team_id=T111" "&team_domain=test-domain" "&channel_id=C111" "&channel_name=random" "&user_id=W111" "&user_name=primary-owner" "&command=%2Fhello-world" "&text=Hi" "&enterprise_id=E111" "&enterprise_name=Org+Name" "&response_url=https%3A%2F%2Fhooks.slack.com%2Fcommands%2FT111%2F111%2Fxxxxx" "&trigger_id=111.111.xxx") timestamp, body = str(int(time())), input headers = self.build_headers(timestamp, body) headers["x-slack-bolt-lazy-only"] = "1" headers["x-slack-bolt-lazy-function-name"] = "say_it" event = { "body": body, "queryStringParameters": {}, "headers": headers, "requestContext": { "http": { "method": "NONE" } }, "isBase64Encoded": False, } response = SlackRequestHandler(app).handle(event, self.context) assert response["statusCode"] == 200 assert self.mock_received_requests["/auth.test"] == 1 assert self.mock_received_requests["/chat.postMessage"] == 1 @mock_lambda 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")
class TestAppUsingMethodsInClass: signing_secret = "secret" valid_token = "xoxb-valid" mock_api_server_base_url = "http://localhost:8888" signature_verifier = SignatureVerifier(signing_secret) web_client = AsyncWebClient( token=valid_token, base_url=mock_api_server_base_url, ) @pytest.fixture def event_loop(self): old_os_env = remove_os_env_temporarily() try: setup_mock_web_api_server(self) loop = asyncio.get_event_loop() yield loop loop.close() cleanup_mock_web_api_server(self) finally: restore_os_env(old_os_env) def test_inspect_behaviors(self): async def f(): pass assert inspect.ismethod(f) is False class A: async def b(self): pass @classmethod async def c(cls): pass @staticmethod async def d(): pass a = A() assert inspect.ismethod(a.b) is True assert inspect.ismethod(A.c) is True assert inspect.ismethod(A.d) is False async def run_app_and_verify(self, app: AsyncApp): payload = { "type": "message_action", "token": "verification_token", "action_ts": "1583637157.207593", "team": { "id": "T111", "domain": "test-test", "enterprise_id": "E111", "enterprise_name": "Org Name", }, "user": {"id": "W111", "name": "test-test"}, "channel": {"id": "C111", "name": "dev"}, "callback_id": "test-shortcut", "trigger_id": "111.222.xxx", "message_ts": "1583636382.000300", "message": { "client_msg_id": "zzzz-111-222-xxx-yyy", "type": "message", "text": "<@W222> test", "user": "******", "ts": "1583636382.000300", "team": "T111", "blocks": [ { "type": "rich_text", "block_id": "d7eJ", "elements": [ { "type": "rich_text_section", "elements": [ {"type": "user", "user_id": "U222"}, {"type": "text", "text": " test"}, ], } ], } ], }, "response_url": "https://hooks.slack.com/app/T111/111/xxx", } timestamp, body = str(int(time())), f"payload={json.dumps(payload)}" request: AsyncBoltRequest = AsyncBoltRequest( body=body, headers={ "content-type": ["application/x-www-form-urlencoded"], "x-slack-signature": [ self.signature_verifier.generate_signature( body=body, timestamp=timestamp, ) ], "x-slack-request-timestamp": [timestamp], }, ) response = await app.async_dispatch(request) assert response.status == 200 await assert_auth_test_count_async(self, 1) await asyncio.sleep(0.5) # wait a bit after auto ack() assert self.mock_received_requests["/chat.postMessage"] == 1 @pytest.mark.asyncio async def test_class_methods(self): app = AsyncApp(client=self.web_client, signing_secret=self.signing_secret) app.use(AwesomeClass.class_middleware) app.shortcut("test-shortcut")(AwesomeClass.class_method) await self.run_app_and_verify(app) @pytest.mark.asyncio async def test_class_methods_uncommon_name(self): app = AsyncApp(client=self.web_client, signing_secret=self.signing_secret) app.use(AwesomeClass.class_middleware) app.shortcut("test-shortcut")(AwesomeClass.class_method2) await self.run_app_and_verify(app) @pytest.mark.asyncio async def test_instance_methods(self): app = AsyncApp(client=self.web_client, signing_secret=self.signing_secret) awesome = AwesomeClass("Slackbot") app.use(awesome.instance_middleware) app.shortcut("test-shortcut")(awesome.instance_method) await self.run_app_and_verify(app) @pytest.mark.asyncio async def test_instance_methods_uncommon_name_1(self): app = AsyncApp(client=self.web_client, signing_secret=self.signing_secret) awesome = AwesomeClass("Slackbot") app.use(awesome.instance_middleware) app.shortcut("test-shortcut")(awesome.instance_method2) await self.run_app_and_verify(app) @pytest.mark.asyncio async def test_instance_methods_uncommon_name_2(self): app = AsyncApp(client=self.web_client, signing_secret=self.signing_secret) awesome = AwesomeClass("Slackbot") app.use(awesome.instance_middleware) app.shortcut("test-shortcut")(awesome.instance_method3) await self.run_app_and_verify(app) @pytest.mark.asyncio async def test_static_methods(self): app = AsyncApp(client=self.web_client, signing_secret=self.signing_secret) app.use(AwesomeClass.static_middleware) app.shortcut("test-shortcut")(AwesomeClass.static_method) await self.run_app_and_verify(app) @pytest.mark.asyncio async def test_invalid_arg_in_func(self): app = AsyncApp(client=self.web_client, signing_secret=self.signing_secret) app.shortcut("test-shortcut")(top_level_function) await self.run_app_and_verify(app)
class TestDjango(TestCase): 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"], } timestamp, body = str(int(time())), json.dumps(input) request = self.rf.post("/slack/events", data=body, content_type="application/json") request.headers = self.build_headers(timestamp, body) response = SlackRequestHandler(app).handle(request) assert response.status_code == 200 assert_auth_test_count(self, 1) def test_shortcuts(self): app = App( client=self.web_client, signing_secret=self.signing_secret, ) def shortcut_handler(ack): ack() app.shortcut("test-shortcut")(shortcut_handler) input = { "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", } timestamp, body = str(int( time())), f"payload={quote(json.dumps(input))}" request = self.rf.post( "/slack/events", data=body, content_type="application/x-www-form-urlencoded", ) request.headers = self.build_headers(timestamp, body) response = SlackRequestHandler(app).handle(request) assert response.status_code == 200 assert_auth_test_count(self, 1) def test_commands(self): app = App( client=self.web_client, signing_secret=self.signing_secret, ) def command_handler(ack): ack() app.command("/hello-world")(command_handler) input = ( "token=verification_token" "&team_id=T111" "&team_domain=test-domain" "&channel_id=C111" "&channel_name=random" "&user_id=W111" "&user_name=primary-owner" "&command=%2Fhello-world" "&text=Hi" "&enterprise_id=E111" "&enterprise_name=Org+Name" "&response_url=https%3A%2F%2Fhooks.slack.com%2Fcommands%2FT111%2F111%2Fxxxxx" "&trigger_id=111.111.xxx") timestamp, body = str(int(time())), input request = self.rf.post( "/slack/events", data=body, content_type="application/x-www-form-urlencoded", ) request.headers = self.build_headers(timestamp, body) response = SlackRequestHandler(app).handle(request) assert response.status_code == 200 assert_auth_test_count(self, 1) def test_commands_process_before_response(self): app = App( client=self.web_client, signing_secret=self.signing_secret, process_before_response=True, ) def command_handler(ack): ack() app.command("/hello-world")(command_handler) input = ( "token=verification_token" "&team_id=T111" "&team_domain=test-domain" "&channel_id=C111" "&channel_name=random" "&user_id=W111" "&user_name=primary-owner" "&command=%2Fhello-world" "&text=Hi" "&enterprise_id=E111" "&enterprise_name=Org+Name" "&response_url=https%3A%2F%2Fhooks.slack.com%2Fcommands%2FT111%2F111%2Fxxxxx" "&trigger_id=111.111.xxx") timestamp, body = str(int(time())), input request = self.rf.post( "/slack/events", data=body, content_type="application/x-www-form-urlencoded", ) request.headers = self.build_headers(timestamp, body) response = SlackRequestHandler(app).handle(request) assert response.status_code == 200 assert_auth_test_count(self, 1) def test_commands_lazy(self): app = App( client=self.web_client, signing_secret=self.signing_secret, ) def command_handler(ack): ack() def lazy_handler(): pass app.command("/hello-world")(ack=command_handler, lazy=[lazy_handler]) input = ( "token=verification_token" "&team_id=T111" "&team_domain=test-domain" "&channel_id=C111" "&channel_name=random" "&user_id=W111" "&user_name=primary-owner" "&command=%2Fhello-world" "&text=Hi" "&enterprise_id=E111" "&enterprise_name=Org+Name" "&response_url=https%3A%2F%2Fhooks.slack.com%2Fcommands%2FT111%2F111%2Fxxxxx" "&trigger_id=111.111.xxx") timestamp, body = str(int(time())), input request = self.rf.post( "/slack/events", data=body, content_type="application/x-www-form-urlencoded", ) request.headers = self.build_headers(timestamp, body) response = SlackRequestHandler(app).handle(request) assert response.status_code == 200 assert_auth_test_count(self, 1) 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")
class TestSlashCommand: signing_secret = "secret" valid_token = "xoxb-valid" mock_api_server_base_url = "http://localhost:8888" signature_verifier = SignatureVerifier(signing_secret) web_client = WebClient( token=valid_token, base_url=mock_api_server_base_url, ) def setup_method(self): self.old_os_env = remove_os_env_temporarily() setup_mock_web_api_server(self) def teardown_method(self): cleanup_mock_web_api_server(self) restore_os_env(self.old_os_env) def generate_signature(self, body: str, timestamp: str): return self.signature_verifier.generate_signature( body=body, timestamp=timestamp, ) def build_headers(self, timestamp: str, body: str): return { "content-type": ["application/x-www-form-urlencoded"], "x-slack-signature": [self.generate_signature(body, timestamp)], "x-slack-request-timestamp": [timestamp], } def build_valid_request(self) -> BoltRequest: timestamp, body = str(int(time())), json.dumps(slash_command_payload) return BoltRequest(body=body, headers=self.build_headers(timestamp, body)) def test_mock_server_is_running(self): resp = self.web_client.api_test() assert resp != None def test_success(self): app = App( client=self.web_client, signing_secret=self.signing_secret, ) app.command("/hello-world")(commander) request = self.build_valid_request() response = app.dispatch(request) assert response.status == 200 assert self.mock_received_requests["/auth.test"] == 1 def test_process_before_response(self): app = App( client=self.web_client, signing_secret=self.signing_secret, process_before_response=True, ) app.command("/hello-world")(commander) request = self.build_valid_request() response = app.dispatch(request) assert response.status == 200 assert self.mock_received_requests["/auth.test"] == 1 def test_failure(self): app = App( client=self.web_client, signing_secret=self.signing_secret, ) request = self.build_valid_request() response = app.dispatch(request) assert response.status == 404 assert self.mock_received_requests["/auth.test"] == 1 app.command("/another-one")(commander) response = app.dispatch(request) assert response.status == 404 assert self.mock_received_requests["/auth.test"] == 2