diff --git a/purge/deactivate_all_users.py b/purge/deactivate_all_users.py index cfd0e20..d90f820 100644 --- a/purge/deactivate_all_users.py +++ b/purge/deactivate_all_users.py @@ -1,82 +1,139 @@ #!/usr/bin/env python3 -from slackclient import SlackClient -import requests import time +import sys + +from slack_sdk import WebClient +from slack_sdk.errors import SlackApiError +import requests # Taken here : https://api.slack.com/custom-integrations/legacy-tokens -SLACK_TOKEN = "" +SLACK_TOKEN = "xoxb-..." # Available in the HTML source code of https://[team].slack.com/admin -WEB_SLACK_TOKEN = "" +WEB_SLACK_TOKEN = "xoxs-..." # Channel containing the members we want to deactivate -DEST_CHANNEL = "general" +DEST_CHANNEL = "FILL ME IN" # Team Slack domain SLACK_DOMAIN = "opentoallctf.slack.com" +# Users we won't ban even if they are in the channel +safe_user_names = { + "7feilee", + "Kroz", + "Diis", + "r00k", + "Ariana", + "Lord_Idiot", + "UnblvR", + "an0n", + "drtychai", + "enio", + "eriner", + "fevral", + "grazfather", + "idr0p", + "kileak", + "mementomori", + "rh0gue", + "sae", + "sferrini", + "uafio", + "vakzz", + "viva", + "waywardsun", +} + def channel_id_by_name(client, name): - """ Fetch channel ID for a given channel name. """ + """Fetch channel ID for a given channel name.""" + limit = 1000 + cursor = None + while True: + resp = client.conversations_list(types="public_channel", limit=limit, cursor=cursor) + channels = resp["channels"] - output = client.api_call("channels.list") - channels = output['channels'] + for channel in channels: + if channel["name"] == name: + return channel["id"] - channel_id = '' - for channel in channels: - if channel['name'] == name: - return channel['id'] + cursor = resp["response_metadata"]["next_cursor"] + if not cursor: + break return None + def get_all_users(client): - """ Fetch all users in the team. Includes deleted/deactivated users. """ + """Fetch all users in the team. Includes deleted/deactivated users.""" + resp = client.users_list() + return resp["members"] - output = client.api_call("users.list") - return output['members'] -sc = SlackClient(SLACK_TOKEN) +def get_all_users_in_channel(client, channel_id): + limit = 1000 + cursor = None + members = [] + while True: + resp = sc.conversations_members(channel=channel_id, limit=1000, cursor=cursor) + cursor = resp["response_metadata"]["next_cursor"] + members += resp["members"] + if not cursor: + break + + return members + +sc = WebClient(SLACK_TOKEN) channel_id = channel_id_by_name(sc, DEST_CHANNEL) if not channel_id: print("[!] No channel ID found for channel '{}'.".format(DEST_CHANNEL)) + sys.exit(1) print("[*] Found channel {} ({}).".format(DEST_CHANNEL, channel_id)) # Get all members members = get_all_users(sc) -members = dict([(member['id'], member) for member in members]) +members = {member["id"]: member for member in members} +print("[*] Found {} total members.".format(len(members))) # Get members in channel -output = sc.api_call("channels.info", channel=channel_id) -members_in_channel = output['channel']['members'] +members_in_channel = get_all_users_in_channel(sc, channel_id) +print("[*] Found {} members in {}".format(len(members_in_channel), DEST_CHANNEL)) + +# Get member ids of the safe list +safe_member_ids = {m_id for m_id, member in members.items() if member["name"] in safe_user_names} +print("[*] Found {} blessed users".format(len(safe_member_ids))) # Filter out bots and deactivated users. -members_to_deactivate = [] -for member_id in members_in_channel: - is_deactivated = members[member_id]['deleted'] - is_bot = members[member_id]['is_bot'] +doomed_members = [] + +doomed_members = [m_id for m_id in members_in_channel if + not members[m_id]["deleted"] and not members[m_id]["is_bot"] + and not m_id in safe_member_ids] - if not is_deactivated and not is_bot: - members_to_deactivate.append(member_id) +print("[*] Found {} doomed members".format(len(doomed_members))) # Deactivate members. # Member deactivation through the slack API is only available for premium teams. # We can bypass this restriction by using a different API endpoint. # The code below simulates an admin manually deactivating users through the # ... web interface. -print("[*] Deactivating {} members.".format(len(members_to_deactivate))) +print("[*] Deactivating {} members.".format(len(doomed_members))) deactivate_url = "https://{}/api/users.admin.setInactive".format(SLACK_DOMAIN) -for member_id in members_to_deactivate: +for member_id in doomed_members: - username = members[member_id]['profile']['display_name'] - data = { "user" : member_id, "token": WEB_SLACK_TOKEN } - headers = { "Content-Type" : "application/x-www-form-urlencoded" } + username = members[member_id]["profile"]["display_name"] + data = {"user": member_id, "token": WEB_SLACK_TOKEN} + headers = {"Content-Type" : "application/x-www-form-urlencoded"} response = requests.post(deactivate_url, data=data, headers=headers) - print("[*] Kicking {} : {}".format(repr(username), member_id)) + print("[*] Banning {} ({})".format(username, member_id)) print(response.text) + if response.json().get("error") == "ratelimited": + time.sleep(1) - # Prevent Slack's rate limiting - time.sleep(1) + # Avoid Slack's rate limiting + time.sleep(0.5) diff --git a/purge/invite_all_users.py b/purge/invite_all_users.py index 694d873..476b376 100644 --- a/purge/invite_all_users.py +++ b/purge/invite_all_users.py @@ -1,49 +1,74 @@ #!/usr/bin/env python3 -from slackclient import SlackClient +import time +import sys + +from slack_sdk import WebClient +from slack_sdk.errors import SlackApiError # Slack API Token -SLACK_TOKEN = "" +SLACK_TOKEN = "xoxb-..." # Channel to invite users too -DEST_CHANNEL = "general" +DEST_CHANNEL = "FILL ME IN" def channel_id_by_name(client, name): - """ Fetch channel ID for a given channel name. """ + """Fetch channel ID for a given channel name.""" + limit = 1000 + cursor = None + while True: + resp = client.conversations_list(types="public_channel", limit=limit, cursor=cursor) + channels = resp["channels"] - output = client.api_call("channels.list") - channels = output['channels'] + for channel in channels: + if channel["name"] == name: + return channel["id"] - channel_id = '' - for channel in channels: - if channel['name'] == name: - return channel['id'] + cursor = resp["response_metadata"]["next_cursor"] + if not cursor: + break return None -def get_all_users(client): - """ Fetch all users in the team. Includes deleted/deactivated users. """ - output = client.api_call("users.list") - return output['members'] +def get_all_users(client): + """Fetch all users in the team. Includes deleted/deactivated users.""" + resp = client.users_list() + return resp["members"] -sc = SlackClient(SLACK_TOKEN) +sc = WebClient(SLACK_TOKEN) channel_id = channel_id_by_name(sc, DEST_CHANNEL) if not channel_id: print("[!] No channel ID found for channel '{}'.".format(DEST_CHANNEL)) + sys.exit(1) print("[*] Found channel {} ({}).".format(DEST_CHANNEL, channel_id)) members = get_all_users(sc) print("[*] Found {} members.".format(len(members))) +# Join the channel, so we can invite to it +sc.conversations_join(channel=channel_id) + # Invite to channel in groups of 30 # Slack limits channel invitations to 30 members per API call. print("[*] Inviting users.") -member_ids = [member['id'] for member in members] +member_ids = [member["id"] for member in members] groups = [member_ids[n:n+30] for n in range(0, len(member_ids), 30)] for group in groups: - sc.api_call("conversations.invite", channel=channel_id, users=','.join(group)) + try: + sc.conversations_invite(channel=channel_id, users=",".join(group)) + except SlackApiError as e: + # Technically we can have up to 30 errors, and some might be bad + # e.response.data["errors"] to check them individually + if "ratelimited" in str(e): + time.sleep(1) + elif "cant_invite_self" in str(e): + continue + elif "already_in_channel" in str(e): + continue + # TODO: Should we bail otherwise? + continue