|
1 | 1 | #!/usr/bin/env python3
|
2 | 2 |
|
3 |
| -from slackclient import SlackClient |
4 |
| -import requests |
5 | 3 | import time
|
| 4 | +import sys |
| 5 | + |
| 6 | +from slack_sdk import WebClient |
| 7 | +from slack_sdk.errors import SlackApiError |
| 8 | +import requests |
6 | 9 |
|
7 | 10 | # Taken here : https://api.slack.com/custom-integrations/legacy-tokens
|
8 |
| -SLACK_TOKEN = "" |
| 11 | +SLACK_TOKEN = "xoxb-..." |
9 | 12 |
|
10 | 13 | # Available in the HTML source code of https://[team].slack.com/admin
|
11 |
| -WEB_SLACK_TOKEN = "" |
| 14 | +WEB_SLACK_TOKEN = "xoxs-..." |
12 | 15 |
|
13 | 16 | # Channel containing the members we want to deactivate
|
14 |
| -DEST_CHANNEL = "general" |
| 17 | +DEST_CHANNEL = "FILL ME IN" |
15 | 18 |
|
16 | 19 | # Team Slack domain
|
17 | 20 | SLACK_DOMAIN = "opentoallctf.slack.com"
|
18 | 21 |
|
| 22 | +# Users we won't ban even if they are in the channel |
| 23 | +safe_user_names = { |
| 24 | + "7feilee", |
| 25 | + "Kroz", |
| 26 | + "Diis", |
| 27 | + "r00k", |
| 28 | + "Ariana", |
| 29 | + "Lord_Idiot", |
| 30 | + "UnblvR", |
| 31 | + "an0n", |
| 32 | + "drtychai", |
| 33 | + "enio", |
| 34 | + "eriner", |
| 35 | + "fevral", |
| 36 | + "grazfather", |
| 37 | + "idr0p", |
| 38 | + "kileak", |
| 39 | + "mementomori", |
| 40 | + "rh0gue", |
| 41 | + "sae", |
| 42 | + "sferrini", |
| 43 | + "uafio", |
| 44 | + "vakzz", |
| 45 | + "viva", |
| 46 | + "waywardsun", |
| 47 | +} |
| 48 | + |
19 | 49 | def channel_id_by_name(client, name):
|
20 |
| - """ Fetch channel ID for a given channel name. """ |
| 50 | + """Fetch channel ID for a given channel name.""" |
| 51 | + limit = 1000 |
| 52 | + cursor = None |
| 53 | + while True: |
| 54 | + resp = client.conversations_list(types="public_channel", limit=limit, cursor=cursor) |
| 55 | + channels = resp["channels"] |
21 | 56 |
|
22 |
| - output = client.api_call("channels.list") |
23 |
| - channels = output['channels'] |
| 57 | + for channel in channels: |
| 58 | + if channel["name"] == name: |
| 59 | + return channel["id"] |
24 | 60 |
|
25 |
| - channel_id = '' |
26 |
| - for channel in channels: |
27 |
| - if channel['name'] == name: |
28 |
| - return channel['id'] |
| 61 | + cursor = resp["response_metadata"]["next_cursor"] |
| 62 | + if not cursor: |
| 63 | + break |
29 | 64 |
|
30 | 65 | return None
|
31 | 66 |
|
| 67 | + |
32 | 68 | def get_all_users(client):
|
33 |
| - """ Fetch all users in the team. Includes deleted/deactivated users. """ |
| 69 | + """Fetch all users in the team. Includes deleted/deactivated users.""" |
| 70 | + resp = client.users_list() |
| 71 | + return resp["members"] |
34 | 72 |
|
35 |
| - output = client.api_call("users.list") |
36 |
| - return output['members'] |
37 | 73 |
|
38 |
| -sc = SlackClient(SLACK_TOKEN) |
| 74 | +def get_all_users_in_channel(client, channel_id): |
| 75 | + limit = 1000 |
| 76 | + cursor = None |
| 77 | + members = [] |
| 78 | + while True: |
| 79 | + resp = sc.conversations_members(channel=channel_id, limit=1000, cursor=cursor) |
| 80 | + cursor = resp["response_metadata"]["next_cursor"] |
| 81 | + members += resp["members"] |
| 82 | + if not cursor: |
| 83 | + break |
| 84 | + |
| 85 | + return members |
| 86 | + |
| 87 | +sc = WebClient(SLACK_TOKEN) |
39 | 88 |
|
40 | 89 | channel_id = channel_id_by_name(sc, DEST_CHANNEL)
|
41 | 90 |
|
42 | 91 | if not channel_id:
|
43 | 92 | print("[!] No channel ID found for channel '{}'.".format(DEST_CHANNEL))
|
| 93 | + sys.exit(1) |
44 | 94 |
|
45 | 95 | print("[*] Found channel {} ({}).".format(DEST_CHANNEL, channel_id))
|
46 | 96 |
|
47 | 97 | # Get all members
|
48 | 98 | members = get_all_users(sc)
|
49 |
| -members = dict([(member['id'], member) for member in members]) |
| 99 | +members = {member["id"]: member for member in members} |
| 100 | +print("[*] Found {} total members.".format(len(members))) |
50 | 101 |
|
51 | 102 | # Get members in channel
|
52 |
| -output = sc.api_call("channels.info", channel=channel_id) |
53 |
| -members_in_channel = output['channel']['members'] |
| 103 | +members_in_channel = get_all_users_in_channel(sc, channel_id) |
| 104 | +print("[*] Found {} members in {}".format(len(members_in_channel), DEST_CHANNEL)) |
| 105 | + |
| 106 | +# Get member ids of the safe list |
| 107 | +safe_member_ids = {m_id for m_id, member in members.items() if member["name"] in safe_user_names} |
| 108 | +print("[*] Found {} blessed users".format(len(safe_member_ids))) |
54 | 109 |
|
55 | 110 | # Filter out bots and deactivated users.
|
56 |
| -members_to_deactivate = [] |
57 |
| -for member_id in members_in_channel: |
58 |
| - is_deactivated = members[member_id]['deleted'] |
59 |
| - is_bot = members[member_id]['is_bot'] |
| 111 | +doomed_members = [] |
| 112 | + |
| 113 | +doomed_members = [m_id for m_id in members_in_channel if |
| 114 | + not members[m_id]["deleted"] and not members[m_id]["is_bot"] |
| 115 | + and not m_id in safe_member_ids] |
60 | 116 |
|
61 |
| - if not is_deactivated and not is_bot: |
62 |
| - members_to_deactivate.append(member_id) |
| 117 | +print("[*] Found {} doomed members".format(len(doomed_members))) |
63 | 118 |
|
64 | 119 | # Deactivate members.
|
65 | 120 | # Member deactivation through the slack API is only available for premium teams.
|
66 | 121 | # We can bypass this restriction by using a different API endpoint.
|
67 | 122 | # The code below simulates an admin manually deactivating users through the
|
68 | 123 | # ... web interface.
|
69 |
| -print("[*] Deactivating {} members.".format(len(members_to_deactivate))) |
| 124 | +print("[*] Deactivating {} members.".format(len(doomed_members))) |
70 | 125 | deactivate_url = "https://{}/api/users.admin.setInactive".format(SLACK_DOMAIN)
|
71 |
| -for member_id in members_to_deactivate: |
| 126 | +for member_id in doomed_members: |
72 | 127 |
|
73 |
| - username = members[member_id]['profile']['display_name'] |
74 |
| - data = { "user" : member_id, "token": WEB_SLACK_TOKEN } |
75 |
| - headers = { "Content-Type" : "application/x-www-form-urlencoded" } |
| 128 | + username = members[member_id]["profile"]["display_name"] |
| 129 | + data = {"user": member_id, "token": WEB_SLACK_TOKEN} |
| 130 | + headers = {"Content-Type" : "application/x-www-form-urlencoded"} |
76 | 131 | response = requests.post(deactivate_url, data=data, headers=headers)
|
77 | 132 |
|
78 |
| - print("[*] Kicking {} : {}".format(repr(username), member_id)) |
| 133 | + print("[*] Banning {} ({})".format(username, member_id)) |
79 | 134 | print(response.text)
|
| 135 | + if response.json().get("error") == "ratelimited": |
| 136 | + time.sleep(1) |
80 | 137 |
|
81 |
| - # Prevent Slack's rate limiting |
82 |
| - time.sleep(1) |
| 138 | + # Avoid Slack's rate limiting |
| 139 | + time.sleep(0.5) |
0 commit comments