def oauth_callback(): # Retrieve the auth code and state from the request params if "code" in request.args: # Verify the state parameter if state_store.consume(request.args["state"]): client = WebClient() # no prepared token needed for this # Complete the installation by calling oauth.v2.access API method oauth_response = client.oauth_v2_access( client_id=settings.slack_client_id, client_secret=settings.slack_client_secret, redirect_uri=settings.auth_redir_url, code=request.args["code"]) installer = oauth_response.get("authed_user", {}) user_token = installer.get("access_token") if user_token is None: return make_response( f"Failure getting user token :( {str(oauth_response)}", 500) settings.slack_token = user_token shutdown_server() return "Thanks for installing this app!" else: return make_response( f"Try the installation again (the state value is already expired)", 400) error = request.args["error"] if "error" in request.args else "" return make_response( f"Something is wrong with the installation (error: {error})", 400)
class SlackHandler(StreamHandler): """Slack log handler Class Inherits: logging.StreamHandler: Base log StreamHandler class. """ def __init__(self, channel: str, slack_bot_token: str = None, username: str = "Gytrash"): """Initialize the stream handler with some specifics for slack. Args: channel (str): Slack channel to publish logs slack_bot_token (str, optional): Slack bot token to use the slack published app. Defaults to None. username (str, optional): Username of the Slack bot. Defaults to "Gytrash". """ StreamHandler.__init__(self) # Initialize a Web API client if slack_bot_token: self.slack_web_client = WebClient(token=slack_bot_token) else: self.slack_web_client = WebClient( token=os.environ["SLACK_BOT_TOKEN"]) self.channel = channel self.username = username def _send_log(self, message: dict): """Posts the formatted message to slack via the web client. Args: message (dict): Slack message dictionary. Follows the blocks API. """ self.slack_web_client.chat_postMessage(**message) def emit(self, message: "logging.LogRecord"): """Emits a message from the handler. Args: message (logging.LogRecord): Log record from the stream. """ assert isinstance(message, logging.LogRecord) slack_message = self.format(message) # List of LogRecord attributes expected when reading the # documentation of the logging module: expected_attributes = ( "args,asctime,created,exc_info,filename,funcName,levelname," "levelno,lineno,module,msecs,message,msg,name,pathname," "process,processName,relativeCreated,stack_info,thread,threadName") for ea in expected_attributes.split(","): if not hasattr(message, ea): print("UNEXPECTED: LogRecord does not have the '{}' field!". format(ea)) slack_message["channel"] = self.channel slack_message["username"] = self.username self._send_log(slack_message)
def __init__(self, ack: Ack, action: dict, client: WebClient, context, logger: logging.Logger, payload: dict, request: BoltRequest): ack() container = request.body.get('container') view: dict = request.body.get(container.get('type')) plugin_help_content: list = [] plugins = payload['plugins'] if plugins is not None: for p in plugins: plugin: Plugin = p if plugin.callback.__doc__ is not None: label = plugin.keyword if type( plugin.keyword) == type("") else plugin.file_name plugin_help_content.append({ "type": "section", "text": { "type": "mrkdwn", "text": f"*{label}:* {plugin.callback.__doc__}" } }) title: dict = view.get('title') title.update(text="Plugin Info") close_button = view.get('close') close_button.update(text="Go Back") client.views_push(trigger_id=request.body.get('trigger_id'), view={ "type": view.get('type'), "title": title, "close": close_button, "blocks": plugin_help_content })
def test_blocks_without_text_arg(self): client = WebClient( base_url="http://localhost:8888", token="xoxb-api_test", team_id="T111" ) with self.assertWarns(UserWarning): resp = client.chat_postMessage(channel="C111", blocks=[]) self.assertTrue(resp["ok"])
def test_issue_605(self): self.text = "This message was sent to verify issue #605" self.called = False @RTMClient.run_on(event="message") def process_messages(**payload): self.logger.info(payload) self.called = True def connect(): self.logger.debug("Starting RTM Client...") self.rtm_client.start() t = threading.Thread(target=connect) t.daemon = True try: t.start() self.assertFalse(self.called) time.sleep(3) self.web_client = WebClient( token=self.bot_token, run_async=False, ) new_message = self.web_client.chat_postMessage( channel=self.channel_id, text=self.text) self.assertFalse("error" in new_message) time.sleep(5) self.assertTrue(self.called) finally: t.join(0.3)
def test_remote_disconnected(self): client = WebClient( base_url="http://localhost:8889", token="xoxb-remote_disconnected", team_id="T111", ) client.auth_test()
def test_attachments_without_fallback(self): client = WebClient( base_url="http://localhost:8888", token="xoxb-api_test", team_id="T111" ) with self.assertWarns(UserWarning): resp = client.chat_postMessage(channel="C111", attachments=[{}]) self.assertTrue(resp["ok"])
def setUp(self): self.logger = logging.getLogger(__name__) self.org_admin_token = os.environ[ SLACK_SDK_TEST_GRID_ORG_ADMIN_USER_TOKEN] self.sync_client: WebClient = WebClient(token=self.org_admin_token) self.async_client: AsyncWebClient = AsyncWebClient( token=self.org_admin_token) self.team_id = os.environ[SLACK_SDK_TEST_GRID_TEAM_ID] self.idp_group_id = os.environ[SLACK_SDK_TEST_GRID_IDP_USERGROUP_ID] if not hasattr(self, "channel_id"): team_admin_token = os.environ[ SLACK_SDK_TEST_GRID_WORKSPACE_ADMIN_USER_TOKEN] client = WebClient(token=team_admin_token) # Only fetching private channels since admin.conversations.restrictAccess methods do not work for public channels convs = client.conversations_list(exclude_archived=True, limit=100, types="private_channel") self.channel_id = next( (c["id"] for c in convs["channels"] if c["name"] != "general"), None) if self.channel_id is None: millis = int(round(time.time() * 1000)) channel_name = f"private-test-channel-{millis}" self.channel_id = client.conversations_create( name=channel_name, is_private=True, )["channel"]["id"]
def oauth_callback(): # Retrieve the auth code and state from the request params if "code" in request.args: state = request.args["state"] if state_store.consume(state): code = request.args["code"] client = WebClient() # no prepared token needed for this app oauth_response = client.oauth_v2_access( client_id=client_id, client_secret=client_secret, code=code) logger.info(f"oauth.v2.access response: {oauth_response}") installed_enterprise = oauth_response.get("enterprise", {}) is_enterprise_install = oauth_response.get("is_enterprise_install") installed_team = oauth_response.get("team", {}) installer = oauth_response.get("authed_user", {}) incoming_webhook = oauth_response.get("incoming_webhook", {}) bot_token = oauth_response.get("access_token") # NOTE: oauth.v2.access doesn't include bot_id in response bot_id = None enterprise_url = None if bot_token is not None: auth_test = client.auth_test(token=bot_token) bot_id = auth_test["bot_id"] if is_enterprise_install is True: enterprise_url = auth_test.get("url") installation = Installation( app_id=oauth_response.get("app_id"), enterprise_id=installed_enterprise.get("id"), enterprise_name=installed_enterprise.get("name"), enterprise_url=enterprise_url, team_id=installed_team.get("id"), team_name=installed_team.get("name"), bot_token=bot_token, bot_id=bot_id, bot_user_id=oauth_response.get("bot_user_id"), bot_scopes=oauth_response.get( "scope"), # comma-separated string user_id=installer.get("id"), user_token=installer.get("access_token"), user_scopes=installer.get("scope"), # comma-separated string incoming_webhook_url=incoming_webhook.get("url"), incoming_webhook_channel=incoming_webhook.get("channel"), incoming_webhook_channel_id=incoming_webhook.get("channel_id"), incoming_webhook_configuration_url=incoming_webhook.get( "configuration_url"), is_enterprise_install=is_enterprise_install, token_type=oauth_response.get("token_type"), ) installation_store.save(installation) return "Thanks for installing this app!" else: return make_response( f"Try the installation again (the state value is already expired)", 400) error = request.args["error"] if "error" in request.args else "" return make_response( f"Something is wrong with the installation (error: {error})", 400)
def test_blocks_as_deserialzed_json_without_text_arg(self): client = WebClient( base_url="http://localhost:8888", token="xoxb-api_test", team_id="T111" ) # this generates a warning because "text" is missing with self.assertWarns(UserWarning): resp = client.chat_postMessage(channel="C111", attachments=json.dumps([])) self.assertTrue(resp["ok"])
def _send_to_listeners(self, message): channels = SlackBotChannel.objects.select_related('bot').filter( bot__is_active=True) for channel in channels: client = WebClient(token=channel.bot.bot_token) client.chat_postMessage(channel=channel.channel_id, attachments=message)
def test_rate_limited(self): client = WebClient( base_url="http://localhost:8888", token="xoxb-rate_limited_only_once", team_id="T111", ) client.retry_handlers.append(rate_limit_error_retry_handler) client.auth_test()
def test_receiving_all_messages(self): self.rtm_client = RTMClient(token=self.bot_token, loop=asyncio.new_event_loop()) self.web_client = WebClient(token=self.bot_token) self.call_count = 0 @RTMClient.run_on(event="message") def send_reply(**payload): self.logger.debug(payload) web_client, data = payload["web_client"], payload["data"] web_client.reactions_add(channel=data["channel"], timestamp=data["ts"], name="eyes") self.call_count += 1 def connect(): self.logger.debug("Starting RTM Client...") self.rtm_client.start() rtm = threading.Thread(target=connect) rtm.daemon = True rtm.start() time.sleep(3) total_num = 10 sender_completion = [] def sent_bulk_message(): for i in range(total_num): text = f"Sent by <https://slack.dev/python-slackclient/|python-slackclient>! ({i})" self.web_client.chat_postMessage(channel="#random", text=text) time.sleep(0.1) sender_completion.append(True) num_of_senders = 3 senders = [] for sender_num in range(num_of_senders): sender = threading.Thread(target=sent_bulk_message) sender.daemon = True sender.start() senders.append(sender) while len(sender_completion) < num_of_senders: time.sleep(1) expected_call_count = total_num * num_of_senders wait_seconds = 0 max_wait = 20 while self.call_count < expected_call_count and wait_seconds < max_wait: time.sleep(1) wait_seconds += 1 self.assertEqual(total_num * num_of_senders, self.call_count, "The RTM handler failed")
def test_if_it_uses_custom_logger(self): logger = CustomLogger("test-logger") client = WebClient( base_url="http://localhost:8888", token="xoxb-api_test", logger=logger, ) client.chat_postMessage(channel="C111", text="hello") self.assertTrue(logger.called)
def per_request(): try: client = WebClient(token=os.environ["SLACK_BOT_TOKEN"], run_async=False) response = client.chat_postMessage( channel="#random", text="You used a new WebClient for posting this message!" ) return str(response) except SlackApiError as e: return make_response(str(e), 400)
def test_blocks_as_deserialized_json_with_text_arg(self): client = WebClient( base_url="http://localhost:8888", token="xoxb-api_test", team_id="T111" ) # this DOESN'T warn because the "text" arg is present resp = client.chat_postMessage( channel="C111", text="test", blocks=json.dumps([]) ) self.assertTrue(resp["ok"])
def test_html_response_body_issue_829(self): client = WebClient(base_url="http://localhost:8888") try: client.users_list(token="xoxb-error_html_response") self.fail("SlackApiError expected here") except err.SlackApiError as e: self.assertTrue( str(e).startswith("Received a response in a non-JSON format: "), e )
def test_attachments_without_fallback_with_text_arg(self): client = WebClient( base_url="http://localhost:8888", token="xoxb-api_test", team_id="T111" ) # this warns because each attachment should have its own fallback, even with "text" with self.assertWarns(UserWarning): resp = client.chat_postMessage( channel="C111", text="test", attachments=[{}] ) self.assertTrue(resp["ok"])
def oauth_callback(): # Retrieve the auth code and state from the request params if "code" in request.args: state = request.args["state"] if state_store.consume(state): code = request.args["code"] try: token_response = WebClient().openid_connect_token( client_id=client_id, client_secret=client_secret, code=code) logger.info(f"openid.connect.token response: {token_response}") id_token = token_response.get("id_token") claims = jwt.decode(id_token, options={"verify_signature": False}, algorithms=["RS256"]) logger.info(f"claims (decoded id_token): {claims}") user_token = token_response.get("access_token") user_info_response = WebClient( token=user_token).openid_connect_userInfo() logger.info( f"openid.connect.userInfo response: {user_info_response}") return f""" <html> <head> <style> body h2 {{ padding: 10px 15px; font-family: verdana; text-align: center; }} </style> </head> <body> <h2>OpenID Connect Claims</h2> <pre>{json.dumps(claims, indent=2)}</pre> <h2>openid.connect.userInfo response</h2> <pre>{json.dumps(user_info_response.data, indent=2)}</pre> </body> </html> """ except Exception: logger.exception( "Failed to perform openid.connect.token API call") return redirect_page_renderer.render_failure_page( "Failed to perform openid.connect.token API call") else: return redirect_page_renderer.render_failure_page( "The state value is already expired") error = request.args["error"] if "error" in request.args else "" return make_response( f"Something is wrong with the installation (error: {error})", 400)
def test_ignores_unmonitored_channels(self): client = WebClient() client.chat_postMessage = MagicMock() subject = LoggingBot(client, [], ["MONITORED"], [], [], []) handled = subject.handle_message("NOT_MONITORED", "user", "text", bot_profile="bot_profile") self.assertFalse(handled) client.chat_postMessage.assert_not_called()
def test_triggers_replies_in_a_thread(self): client = WebClient() client.chat_postMessage = MagicMock() user = "******" channel = "channel" trigger_word = "help me" subject = LoggingBot(client, [trigger_word], [channel], [], [], []) handled = subject.handle_message(channel, user, trigger_word, ts="ts") self.assertTrue(handled) self.assertNotEqual( "", client.chat_postMessage.call_args.kwargs.get("thread_ts", ""))
def test_attachments_as_deserialzed_json_without_text_arg(self): client = WebClient( base_url="http://localhost:8888", token="xoxb-api_test", team_id="T111" ) # this still generates a warning because "text" is missing. The attachment has already # been deserialized, which isn't explicitly prohibited in the docs (but isn't recommended) with self.assertWarns(UserWarning): resp = client.chat_postMessage( channel="C111", attachments=json.dumps([{"fallback": "test"}]) ) self.assertTrue(resp["ok"])
def test_html_response_body_issue_829(self): client = WebClient(base_url="http://localhost:8888") try: client.users_list(token="xoxb-error_html_response") self.fail("SlackApiError expected here") except err.SlackApiError as e: self.assertTrue( str(e).startswith( "Failed to parse the response body: Expecting value: "), e, )
def test_ratelimited(self): client = WebClient( base_url="http://localhost:8888", token="xoxp-ratelimited", team_id="T111", ) client.retry_handlers.append(RateLimitErrorRetryHandler()) try: client.auth_test() self.fail("An exception is expected") except SlackApiError as e: # Just running retries; no assertions for call count so far self.assertEqual(429, e.response.status_code)
def test_triggers_does_not_trigger_in_threads(self): client = WebClient() client.chat_postMessage = MagicMock() user = "******" channel = "channel" trigger_word = "help me" subject = LoggingBot(client, [trigger_word], [channel], [], [], []) handled = subject.handle_message(channel, user, trigger_word, thread_ts="ts") self.assertFalse(handled) client.chat_postMessage.assert_not_called()
def test_ignores_bot_messages(self): client = WebClient() client.chat_postMessage = MagicMock() logging.debug = MagicMock() subject = LoggingBot(client, [], [], [], [], []) handled = subject.handle_message("channel", "user", "text", bot_profile="bot_profile") self.assertFalse(handled) client.chat_postMessage.assert_not_called() logging.debug.assert_called_with(AnyStringWith("bot"))
def test_webhook(self): url = os.environ[SLACK_SDK_TEST_INCOMING_WEBHOOK_URL] webhook = WebhookClient(url) response = webhook.send(text="Hello!") self.assertEqual(200, response.status_code) self.assertEqual("ok", response.body) token = os.environ[SLACK_SDK_TEST_BOT_TOKEN] client = WebClient(token=token) history = client.conversations_history(channel=self.channel_id, limit=1) self.assertIsNotNone(history) actual_text = history["messages"][0]["text"] self.assertEqual("Hello!", actual_text)
def test_ignores_admin_messages(self): client = WebClient() client.chat_postMessage = MagicMock() logging.debug = MagicMock() user = "******" trigger_word = "triggered!" channel = "channel_8" subject = LoggingBot(client, [trigger_word], [channel], [], [user], []) handled = subject.handle_message(channel, user, "text") self.assertFalse(handled) client.chat_postMessage.assert_not_called() logging.debug.assert_called_with(AnyStringWith("admin"))
def setUp(self): if not hasattr(self, "channel_id"): token = os.environ[SLACK_SDK_TEST_BOT_TOKEN] channel_name = os.environ[ SLACK_SDK_TEST_INCOMING_WEBHOOK_CHANNEL_NAME].replace("#", "") client = WebClient(token=token) self.channel_id = None for resp in client.conversations_list(limit=1000): for c in resp["channels"]: if c["name"] == channel_name: self.channel_id = c["id"] break if self.channel_id is not None: break
def test_html_response_body_issue_829(self): retry_handlers = [ServerErrorRetryHandler(max_retry_count=2)] client = WebClient( base_url="http://localhost:8888", retry_handlers=retry_handlers, ) try: client.users_list(token="xoxb-error_html_response") self.fail("SlackApiError expected here") except err.SlackApiError as e: self.assertTrue( str(e).startswith( "Received a response in a non-JSON format: "), e) self.assertEqual(2, retry_handlers[0].call_count)