-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
bb77de7
commit ea508af
Showing
2 changed files
with
132 additions
and
50 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |