def validate_channel_id(name: str, integration_id: Optional[int], input_channel_id: str) -> None: """ In the case that the user is creating an alert via the API and providing the channel ID and name themselves, we want to make sure both values are correct. """ try: integration = Integration.objects.get(id=integration_id) except Integration.DoesNotExist: raise Http404 token = integration.metadata["access_token"] headers = {"Authorization": f"Bearer {token}"} payload = {"channel": input_channel_id} client = SlackClient() try: results = client.get("/conversations.info", headers=headers, params=payload) except ApiError as e: if e.text == "channel_not_found": raise ValidationError("Channel not found. Invalid ID provided.") logger.info("rule.slack.conversation_info_failed", extra={"error": str(e)}) raise IntegrationError("Could not retrieve Slack channel information.") if not isinstance(results, dict): raise IntegrationError("Bad slack channel list response.") stripped_channel_name = strip_channel_name(name) if not stripped_channel_name == results["channel"]["name"]: channel_name = results["channel"]["name"] raise ValidationError( f"Received channel name {channel_name} does not match inputted channel name {stripped_channel_name}." )
def get_users(integration: Integration, organization: Organization) -> Sequence[Mapping[str, Any]]: access_token = (integration.metadata.get("user_access_token") or integration.metadata["access_token"]) headers = {"Authorization": f"Bearer {access_token}"} client = SlackClient() user_list = [] next_cursor = None for i in range(SLACK_GET_USERS_PAGE_LIMIT): try: next_users = client.get( "/users.list", headers=headers, params={ "limit": SLACK_GET_USERS_PAGE_SIZE, "cursor": next_cursor }, ) except ApiError as e: logger.info( "post_install.fail.slack_users.list", extra={ "error": str(e), "organization": organization.slug, "integration_id": integration.id, }, ) break user_list += next_users["members"] next_cursor = next_users["response_metadata"]["next_cursor"] if not next_cursor: break return user_list
def get_channel_id_with_timeout( integration: "Integration", name: Optional[str], timeout: int, ) -> Tuple[str, Optional[str], bool]: """ Fetches the internal slack id of a channel. :param integration: The slack integration :param name: The name of the channel :param timeout: Our self-imposed time limit. :return: a tuple of three values 1. prefix: string (`"#"` or `"@"`) 2. channel_id: string or `None` 3. timed_out: boolean (whether we hit our self-imposed time limit) """ headers = { "Authorization": f"Bearer {integration.metadata['access_token']}" } payload = { "exclude_archived": False, "exclude_members": True, "types": "public_channel,private_channel", } list_types = LIST_TYPES time_to_quit = time.time() + timeout client = SlackClient() id_data: Optional[Tuple[str, Optional[str], bool]] = None found_duplicate = False prefix = "" for list_type, result_name, prefix in list_types: cursor = "" while True: endpoint = f"/{list_type}.list" try: # Slack limits the response of `<list_type>.list` to 1000 channels items = client.get(endpoint, headers=headers, params=dict(payload, cursor=cursor, limit=1000)) except ApiRateLimitedError as e: logger.info(f"rule.slack.{list_type}_list_rate_limited", extra={"error": str(e)}) raise e except ApiError as e: logger.info(f"rule.slack.{list_type}_list_rate_limited", extra={"error": str(e)}) return prefix, None, False if not isinstance(items, dict): continue for c in items[result_name]: # The "name" field is unique (this is the username for users) # so we return immediately if we find a match. # convert to lower case since all names in Slack are lowercase if name and c["name"].lower() == name.lower(): return prefix, c["id"], False # If we don't get a match on a unique identifier, we look through # the users' display names, and error if there is a repeat. if list_type == "users": profile = c.get("profile") if profile and profile.get("display_name") == name: if id_data: found_duplicate = True else: id_data = (prefix, c["id"], False) cursor = items.get("response_metadata", {}).get("next_cursor", None) if time.time() > time_to_quit: return prefix, None, True if not cursor: break if found_duplicate: raise DuplicateDisplayNameError(name) elif id_data: return id_data return prefix, None, False