3 changed files with 321 additions and 279 deletions
@ -0,0 +1,4 @@ |
|||||
|
[flake8] |
||||
|
exclude = .git |
||||
|
# 79 character limit is impractical with a 4 space indent... |
||||
|
ignore = E501 |
||||
@ -1,309 +1,347 @@ |
|||||
#!/usr/bin/env python |
#!/usr/bin/env python |
||||
|
|
||||
import sys |
|
||||
import os |
import os |
||||
|
import sys |
||||
import yaml |
import yaml |
||||
import logging |
import logging |
||||
import argparse |
import argparse |
||||
from slackclient import SlackClient |
from slackclient import SlackClient |
||||
|
|
||||
program_version="1.0" |
program_version = "1.0" |
||||
|
|
||||
logger = logging.getLogger(__name__) |
logger = logging.getLogger(__name__) |
||||
|
|
||||
|
|
||||
def load_config(conf_path=None, team=None): |
def load_config(conf_path=None, team=None): |
||||
# Try the usual places to store an API token |
# Try the usual places to store an API token |
||||
# First token wins |
# First token wins |
||||
api_token = None |
api_token = None |
||||
bot_token = None |
bot_token = None |
||||
config_data = {} |
config_data = {} |
||||
homedir = os.path.expanduser("~") |
homedir = os.path.expanduser("~") |
||||
scriptdir = os.path.dirname(os.path.realpath(__file__)) |
scriptdir = os.path.dirname(os.path.realpath(__file__)) |
||||
|
|
||||
# 1. Specified config file |
# 1. Specified config file |
||||
if conf_path: |
if conf_path: |
||||
logger.debug("Skipping standard config locations because config path passed in on the command line.") |
logger.debug("Skipping standard locations because config path passed on the command line.") |
||||
# 2. ~/.config/slack/config.yml |
# 2. ~/.config/slack/config.yml |
||||
elif os.path.isfile(os.path.join(homedir,".config","belay","config.yml")): |
elif os.path.isfile(os.path.join(homedir, ".config", "belay", "config.yml")): |
||||
conf_path = os.path.join(homedir,".config","belay","config.yml") |
conf_path = os.path.join(homedir, ".config", "belay", "config.yml") |
||||
logger.info("Found config file in Home Directory: %s", conf_path) |
logger.info("Found config file in Home Directory: %s", conf_path) |
||||
# 3. ./config.yml |
# 3. ./config.yml |
||||
elif os.path.isfile(os.path.join(scriptdir,"config.yml")): |
elif os.path.isfile(os.path.join(scriptdir, "config.yml")): |
||||
conf_path = os.path.join(scriptdir,"config.yml") |
conf_path = os.path.join(scriptdir, "config.yml") |
||||
logger.info("Found config file in current script directory: %s", conf_path) |
logger.info("Found config file in current script directory: %s", conf_path) |
||||
# OK, now actually pull values from the configs |
# OK, now actually pull values from the configs |
||||
if conf_path: |
if conf_path: |
||||
logger.info("Attempting to load config from file.") |
logger.info("Attempting to load config from file.") |
||||
with open(conf_path, "r") as config: |
with open(conf_path, "r") as config: |
||||
config_data = yaml.load(config, Loader=yaml.loader.BaseLoader) |
config_data = yaml.load(config, Loader=yaml.loader.BaseLoader) |
||||
logger.debug("Loaded config: %s", config_data) |
logger.debug("Loaded config: %s", config_data) |
||||
if "teams" in config_data: |
if "teams" in config_data: |
||||
logger.debug("This is a multi-team config file. Script is looking for team: %s", team) |
logger.debug("This is a multi-team config file. Script is looking for team: %s", team) |
||||
if not team: |
if not team: |
||||
raise ValueError("No team specified with multi-team config. Please indicate which team you'd like to use.") |
raise ValueError("No team specified with multi-team config. " + |
||||
elif team not in config_data["teams"]: |
"Please indicate which team you'd like to use.") |
||||
raise ValueError("Specified team '"+team+"' not present in config file. Valid choices are: "+", ".join(config_data["teams"].keys())) |
elif team not in config_data["teams"]: |
||||
else: |
raise ValueError("Specified team '" + team + |
||||
logger.debug("Team '%s' found. Selecting only data for that team.", team) |
"' not present in config file. Valid choices are: " + |
||||
config_data = config_data["teams"][team] |
", ".join(config_data["teams"].keys())) |
||||
# Allow people to set keys in the env if they don't want them in files |
else: |
||||
# Clear bot if we find API, to try to avoid mixing teams |
logger.debug("Team '%s' found. Selecting only data for that team.", team) |
||||
if "SLACK_API_TOKEN" in os.environ: |
config_data = config_data["teams"][team] |
||||
config_data.pop("bot_token", 0) |
# Allow people to set keys in the env if they don't want them in files |
||||
config_data["api_token"] = os.environ["SLACK_API_TOKEN"] |
# Clear bot if we find API, to try to avoid mixing teams |
||||
logger.info("Found API token in environment, adding to config from file.") |
if "SLACK_API_TOKEN" in os.environ: |
||||
logger.debug("API Token: %s", api_token) |
config_data.pop("bot_token", 0) |
||||
logger.info("Checking environment for SLACK_BOT_TOKEN variable...") |
config_data["api_token"] = os.environ["SLACK_API_TOKEN"] |
||||
if "SLACK_BOT_TOKEN" in os.environ: |
logger.info("Found API token in environment, adding to config from file.") |
||||
config_data["bot_token"] = os.environ["SLACK_BOT_TOKEN"] |
logger.debug("API Token: %s", api_token) |
||||
logger.info("Found bot token in environment, adding to config from file.") |
logger.info("Checking environment for SLACK_BOT_TOKEN variable...") |
||||
logger.debug("Bot Token: %s", bot_token) |
if "SLACK_BOT_TOKEN" in os.environ: |
||||
|
config_data["bot_token"] = os.environ["SLACK_BOT_TOKEN"] |
||||
|
logger.info("Found bot token in environment, adding to config from file.") |
||||
|
logger.debug("Bot Token: %s", bot_token) |
||||
|
|
||||
if "api_token" not in config_data: |
if "api_token" not in config_data: |
||||
raise RuntimeError("No API token not found.") |
raise RuntimeError("No API token not found.") |
||||
if "bot_token" not in config_data: |
if "bot_token" not in config_data: |
||||
logger.warn("No bot token provided. Assuming API token is legacy token. Use of legacy tokens is a potential security issue and we strongly recommend switching to the new token format.") |
logger.warn("No bot token provided. Assuming API token is legacy token. " + |
||||
return config_data |
"Use of legacy tokens is a potential security issue and we strongly " + |
||||
|
"recommend switching to the new token format.") |
||||
|
return config_data |
||||
|
|
||||
def belay(config): |
|
||||
slack_api = SlackClient(config["api_token"]) |
|
||||
api_test = slack_api.api_call("auth.test") |
|
||||
logger.debug("API Token auth.test results: %s", api_test) |
|
||||
if not api_test["ok"]: |
|
||||
raise ValueError("API Token is invalid.") |
|
||||
api_user = slack_api.api_call("users.info", user=api_test["user_id"]) |
|
||||
logger.debug("User info for API token user: %s", api_user) |
|
||||
if api_user["user"]["is_bot"]: |
|
||||
raise ValueError("API Token is a bot token. This will not work.") |
|
||||
if "bot_token" in config: |
|
||||
slack_bot = SlackClient(config["bot_token"]) |
|
||||
bot_test = slack_bot.api_call("auth.test") |
|
||||
logger.debug("Bot Token auth.test results: %s", bot_test) |
|
||||
if not bot_test["ok"]: |
|
||||
raise ValueError("Bot Token is invalid.") |
|
||||
bot_user = slack_bot.api_call("users.info", user=bot_test["user_id"]) |
|
||||
logger.debug("User info for Bot token user: %s", bot_user) |
|
||||
if not bot_user["user"]["is_bot"]: |
|
||||
raise ValueError("Bot Token does not correspond to a bot user.") |
|
||||
else: |
|
||||
slack_bot = slack_api |
|
||||
integration_issues = check_integrations(slack_api, config) |
|
||||
if integration_issues: |
|
||||
logger.info("Found the following integration issues: %s", integration_issues) |
|
||||
notify_problems(integration_issues, config, slack_bot, "Problem Integrations:", "integration_name", "date") |
|
||||
else: |
|
||||
logger.info("No integration issues found.") |
|
||||
user_issues = check_users(slack_api, config) |
|
||||
if user_issues: |
|
||||
logger.info("Found the following user issues: %s", user_issues) |
|
||||
notify_problems(user_issues, config, slack_bot, "Problem Users:", "name", "name") |
|
||||
else: |
|
||||
logger.info("No user issues found.") |
|
||||
|
|
||||
def check_integrations(api_client, config): |
def belay(config): |
||||
if "skip_integrations" in config and config["skip_integrations"]: |
slack_api = SlackClient(config["api_token"]) |
||||
return {} |
api_test = slack_api.api_call("auth.test") |
||||
result = api_client.api_call("team.integrationLogs") |
logger.debug("API Token auth.test results: %s", api_test) |
||||
logger.debug("Integration log results: %s", result) |
if not api_test["ok"]: |
||||
if not result["ok"]: |
raise ValueError("API Token is invalid.") |
||||
raise RuntimeError("API Call encountered an error while getting initial integrations: "+unicode(result)) |
api_user = slack_api.api_call("users.info", user=api_test["user_id"]) |
||||
iLogs = result["logs"] |
logger.debug("User info for API token user: %s", api_user) |
||||
if result["paging"]["pages"] > 1: |
if api_user["user"]["is_bot"]: |
||||
logger.info("Multiple pages of integration logs exist. Pulling remaining %s pages...", result["paging"]["pages"] - 1) |
raise ValueError("API Token is a bot token. This will not work.") |
||||
while result["paging"]["page"] < result["paging"]["pages"]: |
if "bot_token" in config: |
||||
nextPage = result["paging"]["page"] + 1 |
slack_bot = SlackClient(config["bot_token"]) |
||||
logger.info("Pulling page %s.", nextPage) |
bot_test = slack_bot.api_call("auth.test") |
||||
result = api_client.api_call("team.integrationLogs", page=nextPage) |
logger.debug("Bot Token auth.test results: %s", bot_test) |
||||
logger.debug("Page %s: %s", nextPage, result) |
if not bot_test["ok"]: |
||||
if not result["ok"]: |
raise ValueError("Bot Token is invalid.") |
||||
raise RuntimeError("API Call encountered an error while getting additional integrations: "+unicode(result)) |
bot_user = slack_bot.api_call("users.info", user=bot_test["user_id"]) |
||||
iLogs.extend(result["logs"]) |
logger.debug("User info for Bot token user: %s", bot_user) |
||||
integrations = {} |
if not bot_user["user"]["is_bot"]: |
||||
if "integration_whitelist" in config: |
raise ValueError("Bot Token does not correspond to a bot user.") |
||||
integration_whitelist = config["integration_whitelist"] |
|
||||
else: |
|
||||
integration_whitelist = {} |
|
||||
for log in iLogs: |
|
||||
if log["change_type"] in ["added", "enabled", "updated", "expanded", "reissued"]: |
|
||||
status = "active" |
|
||||
elif log["change_type"] == "removed": |
|
||||
status = "removed" |
|
||||
elif log["change_type"] == "disabled": |
|
||||
status = "disabled" |
|
||||
else: |
else: |
||||
status = "unknown" |
slack_bot = slack_api |
||||
logger.warn("Unknown change type (%s) in integration log: %s", log["change_type"], log) |
integration_issues = check_integrations(slack_api, config) |
||||
if "scope" in log and log["scope"]: |
if integration_issues: |
||||
scopes = log["scope"].split(",") |
logger.info("Found the following integration issues: %s", integration_issues) |
||||
|
notify_problems(integration_issues, config, slack_bot, |
||||
|
"Problem Integrations:", "integration_name", "date") |
||||
else: |
else: |
||||
scopes = [] |
logger.info("No integration issues found.") |
||||
if "app_id" in log: |
user_issues = check_users(slack_api, config) |
||||
int_id = log["app_id"] |
if user_issues: |
||||
int_name = log["app_type"] |
logger.info("Found the following user issues: %s", user_issues) |
||||
elif "service_id" in log: |
notify_problems(user_issues, config, slack_bot, "Problem Users:", "name", "name") |
||||
int_id = log["service_id"] |
else: |
||||
int_name = log["service_type"] |
logger.info("No user issues found.") |
||||
elif log["user_id"] == 0 and log["change_type"] == "removed": |
|
||||
# No idea what these are, but they don't have any useful fields, so skip them. |
|
||||
continue |
def check_integrations(api_client, config): |
||||
|
if "skip_integrations" in config and config["skip_integrations"]: |
||||
|
return {} |
||||
|
result = api_client.api_call("team.integrationLogs") |
||||
|
logger.debug("Integration log results: %s", result) |
||||
|
if not result["ok"]: |
||||
|
raise RuntimeError("API Call encountered an error while getting initial integrations: " + |
||||
|
unicode(result)) |
||||
|
iLogs = result["logs"] |
||||
|
if result["paging"]["pages"] > 1: |
||||
|
logger.info("Multiple pages of integration logs exist. Pulling remaining %s pages...", |
||||
|
result["paging"]["pages"] - 1) |
||||
|
while result["paging"]["page"] < result["paging"]["pages"]: |
||||
|
nextPage = result["paging"]["page"] + 1 |
||||
|
logger.info("Pulling page %s.", nextPage) |
||||
|
result = api_client.api_call("team.integrationLogs", page=nextPage) |
||||
|
logger.debug("Page %s: %s", nextPage, result) |
||||
|
if not result["ok"]: |
||||
|
raise RuntimeError("API Call encountered an error while getting more integrations: " + |
||||
|
unicode(result)) |
||||
|
iLogs.extend(result["logs"]) |
||||
|
integrations = {} |
||||
|
if "integration_whitelist" in config: |
||||
|
integration_whitelist = config["integration_whitelist"] |
||||
else: |
else: |
||||
logger.warn("Unknown integration type: %s", log) |
integration_whitelist = {} |
||||
continue |
for log in iLogs: |
||||
if int_id not in integrations: |
if log["change_type"] in ["added", "enabled", "updated", "expanded", "reissued"]: |
||||
integrations[int_id] = {"integration_name": int_name, "app_id": int_id, "status": status, "scopes": scopes, "user_id": log["user_id"], "user_name": log["user_name"], "date": log["date"]} |
status = "active" |
||||
if "reason" in log: |
elif log["change_type"] == "removed": |
||||
integrations[int_id]["reason"] = log["reason"] |
status = "removed" |
||||
if "channel" in log: |
elif log["change_type"] == "disabled": |
||||
integrations[int_id]["channel"] = log["channel"] |
status = "disabled" |
||||
problem_integrations = [] |
else: |
||||
if "integration_issue_whitelist" in config: |
status = "unknown" |
||||
global_whitelist = config["integration_issue_whitelist"] |
logger.warn("Unknown change type (%s) in integration log: %s", log["change_type"], log) |
||||
else: |
if "scope" in log and log["scope"]: |
||||
global_whitelist = [] |
scopes = log["scope"].split(",") |
||||
for int_id in integrations: |
else: |
||||
integration = integrations[int_id] |
scopes = [] |
||||
if integration["status"] == "removed": |
if "app_id" in log: |
||||
continue |
int_id = log["app_id"] |
||||
if int_id in integration_whitelist: |
int_name = log["app_type"] |
||||
issue_whitelist = global_whitelist.extend(integration_whitelist[int_id]) |
elif "service_id" in log: |
||||
|
int_id = log["service_id"] |
||||
|
int_name = log["service_type"] |
||||
|
elif log["user_id"] == 0 and log["change_type"] == "removed": |
||||
|
# No idea what these are, but they don't have any useful fields, so skip them. |
||||
|
continue |
||||
|
else: |
||||
|
logger.warn("Unknown integration type: %s", log) |
||||
|
continue |
||||
|
if int_id not in integrations: |
||||
|
integrations[int_id] = {"integration_name": int_name, "app_id": int_id, |
||||
|
"status": status, "scopes": scopes, "user_id": log["user_id"], |
||||
|
"user_name": log["user_name"], "date": log["date"]} |
||||
|
if "reason" in log: |
||||
|
integrations[int_id]["reason"] = log["reason"] |
||||
|
if "channel" in log: |
||||
|
integrations[int_id]["channel"] = log["channel"] |
||||
|
problem_integrations = [] |
||||
|
if "integration_issue_whitelist" in config: |
||||
|
global_whitelist = config["integration_issue_whitelist"] |
||||
else: |
else: |
||||
issue_whitelist = global_whitelist |
global_whitelist = [] |
||||
problems = [] |
for int_id in integrations: |
||||
logger.debug("Checking for issues with integration: %s", integration) |
integration = integrations[int_id] |
||||
if "MAX" in integration["scopes"] and "legacy" not in issue_whitelist: |
if integration["status"] == "removed": |
||||
problems.append("Legacy integration with full access to act as the user") |
continue |
||||
if "admin" in integration["scopes"] and "admin" not in issue_whitelist: |
if int_id in integration_whitelist: |
||||
problems.append("Admin permission") |
issue_whitelist = global_whitelist.extend(integration_whitelist[int_id]) |
||||
if "chat:write:user" in integration["scopes"] and "chat:write:user" not in issue_whitelist: |
else: |
||||
problems.append("Can chat as user") |
issue_whitelist = global_whitelist |
||||
if "channels:history" in integration["scopes"] and "channels:history" not in issue_whitelist: |
problems = [] |
||||
problems.append("Can access channel history for public channels") |
logger.debug("Checking for issues with integration: %s", integration) |
||||
if "files:read" in integration["scopes"] and "files:read" not in issue_whitelist: |
if "MAX" in integration["scopes"] and "legacy" not in issue_whitelist: |
||||
problems.append("Can read uploaded files") |
problems.append("Legacy integration with full access to act as the user") |
||||
if "files:write:user" in integration["scopes"] and "files:write:user" not in issue_whitelist: |
if "admin" in integration["scopes"] and "admin" not in issue_whitelist: |
||||
problems.append("Can modify/delete existing files") |
problems.append("Admin permission") |
||||
if "groups:history" in integration["scopes"] and "groups:history" not in issue_whitelist: |
if "chat:write:user" in integration["scopes"] and "chat:write:user" not in issue_whitelist: |
||||
problems.append("Can access channel history for private channels") |
problems.append("Can chat as user") |
||||
if "im:history" in integration["scopes"] and "im:history" not in issue_whitelist: |
if "channels:history" in integration["scopes"] and "channels:history" not in issue_whitelist: |
||||
problems.append("Can access channel history for private IMs") |
problems.append("Can access channel history for public channels") |
||||
if "mpim:history" in integration["scopes"] and "mpim:history" not in issue_whitelist: |
if "files:read" in integration["scopes"] and "files:read" not in issue_whitelist: |
||||
problems.append("Can access channel history for multi-party IMs") |
problems.append("Can read uploaded files") |
||||
if "pins:read" in integration["scopes"] and "pins:read" not in issue_whitelist: |
if "files:write:user" in integration["scopes"] and "files:write:user" not in issue_whitelist: |
||||
problems.append("Can access channel pinned messages/files") |
problems.append("Can modify/delete existing files") |
||||
if "search:read" in integration["scopes"] and "search:read" not in issue_whitelist: |
if "groups:history" in integration["scopes"] and "groups:history" not in issue_whitelist: |
||||
problems.append("Can search team files and messages") |
problems.append("Can access channel history for private channels") |
||||
if problems: |
if "im:history" in integration["scopes"] and "im:history" not in issue_whitelist: |
||||
integration["problems"] = problems |
problems.append("Can access channel history for private IMs") |
||||
problem_integrations.append(integration) |
if "mpim:history" in integration["scopes"] and "mpim:history" not in issue_whitelist: |
||||
return problem_integrations |
problems.append("Can access channel history for multi-party IMs") |
||||
|
if "pins:read" in integration["scopes"] and "pins:read" not in issue_whitelist: |
||||
|
problems.append("Can access channel pinned messages/files") |
||||
|
if "search:read" in integration["scopes"] and "search:read" not in issue_whitelist: |
||||
|
problems.append("Can search team files and messages") |
||||
|
if problems: |
||||
|
integration["problems"] = problems |
||||
|
problem_integrations.append(integration) |
||||
|
return problem_integrations |
||||
|
|
||||
|
|
||||
def check_users(api_client, config): |
def check_users(api_client, config): |
||||
result = api_client.api_call("users.list", presence=False, limit=100) |
result = api_client.api_call("users.list", presence=False, limit=100) |
||||
logger.debug("User list results: %s", result) |
|
||||
if not result["ok"]: |
|
||||
raise RuntimeError("API Call encountered an error while getting initial user list: "+unicode(result)) |
|
||||
users = result["members"] |
|
||||
while "next_cursor" in result["response_metadata"] and result["response_metadata"]["next_cursor"]: |
|
||||
logger.info("Further pages of users exist. Pulling next page...") |
|
||||
result = api_client.api_call("users.list", presence=False, limit=100, cursor=result["response_metadata"]["next_cursor"]) |
|
||||
logger.debug("User list results: %s", result) |
logger.debug("User list results: %s", result) |
||||
if not result["ok"]: |
if not result["ok"]: |
||||
raise RuntimeError("API Call encountered an error while getting additional user list: "+unicode(result)) |
raise RuntimeError("API Call encountered an error while getting initial user list: " + |
||||
users.extend(result["members"]) |
unicode(result)) |
||||
problem_users = [] |
users = result["members"] |
||||
retained_keys = ["real_name", "id", "team_id", "name", "problems", "has_2fa", "two_factor_type", "updated", "is_owner", "is_admin"] |
while "next_cursor" in result["response_metadata"] and result["response_metadata"]["next_cursor"]: |
||||
if "user_whitelist" in config: |
logger.info("Further pages of users exist. Pulling next page...") |
||||
user_whitelist = config["user_whitelist"] |
result = api_client.api_call("users.list", presence=False, limit=100, |
||||
else: |
cursor=result["response_metadata"]["next_cursor"]) |
||||
user_whitelist = {} |
logger.debug("User list results: %s", result) |
||||
if "user_issue_whitelist" in config: |
if not result["ok"]: |
||||
global_whitelist = config["user_issue_whitelist"] |
raise RuntimeError("API Call encountered an error while getting additional users: " + |
||||
else: |
unicode(result)) |
||||
global_whitelist = [] |
users.extend(result["members"]) |
||||
for user in users: |
problem_users = [] |
||||
logger.debug("Checking for issues with user: %s", user) |
retained_keys = ["real_name", "id", "team_id", "name", "problems", |
||||
if user["id"] == "USLACKBOT": |
"has_2fa", "two_factor_type", "updated", "is_owner", "is_admin"] |
||||
# Special case Slackbot, since it lacks the fields of other users/bots |
if "user_whitelist" in config: |
||||
continue |
user_whitelist = config["user_whitelist"] |
||||
if user["deleted"] or user["is_bot"]: |
|
||||
continue |
|
||||
if user["id"] in user_whitelist: |
|
||||
issue_whitelist = global_whitelist.extend(user_whitelist[user["id"]]) |
|
||||
else: |
else: |
||||
issue_whitelist = global_whitelist |
user_whitelist = {} |
||||
if not user["has_2fa"] and "2fa" not in issue_whitelist: |
if "user_issue_whitelist" in config: |
||||
user["problems"] = ["User does not have 2FA enabled"] |
global_whitelist = config["user_issue_whitelist"] |
||||
if user["has_2fa"] and user["two_factor_type"] == "sms" and "sms" not in issue_whitelist: |
else: |
||||
user["problems"] = ["User is using less-secure SMS-based 2FA"] |
global_whitelist = [] |
||||
if "problems" in user: |
for user in users: |
||||
problem_user = {k:v for k,v in user.iteritems() if k in retained_keys} |
logger.debug("Checking for issues with user: %s", user) |
||||
problem_users.append(problem_user) |
if user["id"] == "USLACKBOT": |
||||
return problem_users |
# Special case Slackbot, since it lacks the fields of other users/bots |
||||
|
continue |
||||
|
if user["deleted"] or user["is_bot"]: |
||||
|
continue |
||||
|
if user["id"] in user_whitelist: |
||||
|
issue_whitelist = global_whitelist.extend(user_whitelist[user["id"]]) |
||||
|
else: |
||||
|
issue_whitelist = global_whitelist |
||||
|
if not user["has_2fa"] and "2fa" not in issue_whitelist: |
||||
|
user["problems"] = ["User does not have 2FA enabled"] |
||||
|
if user["has_2fa"] and user["two_factor_type"] == "sms" and "sms" not in issue_whitelist: |
||||
|
user["problems"] = ["User is using less-secure SMS-based 2FA"] |
||||
|
if "problems" in user: |
||||
|
problem_user = {k: v for k, v in user.iteritems() if k in retained_keys} |
||||
|
problem_users.append(problem_user) |
||||
|
return problem_users |
||||
|
|
||||
|
|
||||
def notify_problems(problems, config, slack_bot, heading="Problems:", item_name="name", sort_field=None): |
def notify_problems(problems, config, slack_bot, heading="Problems:", |
||||
indent = "\t" |
item_name="name", sort_field=None): |
||||
if "output_channel" in config: |
indent = "\t" |
||||
indent = "\t\t" |
if "output_channel" in config: |
||||
if sort_field: |
indent = "\t\t" |
||||
problems = sorted(problems, key=lambda k: k[sort_field]) |
if sort_field: |
||||
prob_strings = [] |
problems = sorted(problems, key=lambda k: k[sort_field]) |
||||
for problem in problems: |
prob_strings = [] |
||||
fmt_problem = problem[item_name]+": "+", ".join(problem["problems"]) |
for problem in problems: |
||||
for item in sorted(problem.items()): |
fmt_problem = problem[item_name] + ": " + ", ".join(problem["problems"]) |
||||
if not hasattr(item[1], "strip") and (hasattr(item[1], "__getitem__") or hasattr(item[1], "__iter__")): |
for item in sorted(problem.items()): |
||||
val = ", ".join(item[1]) |
if not hasattr(item[1], "strip") and \ |
||||
else: |
(hasattr(item[1], "__getitem__") or hasattr(item[1], "__iter__")): |
||||
val = unicode(item[1]) |
val = ", ".join(item[1]) |
||||
fmt_problem= fmt_problem+"\n"+indent+item[0]+": "+val |
else: |
||||
prob_strings.append(fmt_problem) |
val = unicode(item[1]) |
||||
formatted_problems = "\n\n".join(prob_strings) |
fmt_problem = fmt_problem + "\n" + indent + item[0] + ": " + val |
||||
if "output_channel" in config: |
prob_strings.append(fmt_problem) |
||||
if len(formatted_problems) < 3000: |
formatted_problems = "\n\n".join(prob_strings) |
||||
attachment = [{ |
if "output_channel" in config: |
||||
"fallback": heading+"\n"+formatted_problems, |
if len(formatted_problems) < 3000: |
||||
"title": heading, |
attachment = [{ |
||||
"text": formatted_problems, |
"fallback": heading + "\n" + formatted_problems, |
||||
"color": "#ffe600" |
"title": heading, |
||||
}] |
"text": formatted_problems, |
||||
result = slack_bot.api_call("chat.postMessage", channel=config["output_channel"], attachments=attachment, as_user=True) |
"color": "#ffe600" |
||||
|
}] |
||||
|
result = slack_bot.api_call("chat.postMessage", |
||||
|
channel=config["output_channel"], |
||||
|
attachments=attachment, as_user=True) |
||||
|
else: |
||||
|
# For bigger files we use a snippet. This has a 1MB limit. |
||||
|
# If you have more problems... well add this to the list. |
||||
|
result = slack_bot.api_call("files.upload", |
||||
|
content=formatted_problems, |
||||
|
title=heading, filetype="text") |
||||
|
logger.info("Message too large. Uploaded as file: %s", result) |
||||
|
if not result["ok"]: |
||||
|
raise RuntimeError("API Call encountered an error while uploading file: " + |
||||
|
unicode(result)) |
||||
|
msg = "*" + heading + "*\n\nToo many " + "issues to post directly.\nSee <" + \ |
||||
|
result["file"]["url_private"] + "|the uploaded file> for details." |
||||
|
result = slack_bot.api_call("chat.postMessage", channel=config["output_channel"], |
||||
|
text=msg, unfurl_links=True, as_user=True) |
||||
|
logger.info("Message posted. Result: %s", result) |
||||
|
if not result["ok"]: |
||||
|
raise RuntimeError("API Call encountered an error while posting message: " + |
||||
|
unicode(result)) |
||||
else: |
else: |
||||
# For bigger files we use a snippet. This has a 1MB limit. If you have more problems... well add that to the list. |
print heading |
||||
result = slack_bot.api_call("files.upload", content=formatted_problems, title=heading, filetype="text") |
print "" |
||||
logger.info("Message too large. Uploaded as file: %s", result) |
print formatted_problems |
||||
if not result["ok"]: |
|
||||
raise RuntimeError("API Call encountered an error while uploading file: "+unicode(result)) |
|
||||
result = slack_bot.api_call("chat.postMessage", channel=config["output_channel"], text="*"+heading+"*\n\nToo many issues to post directly.\nSee <"+result["file"]["url_private"]+"|the uploaded file> for more information.", unfurl_links=True, as_user=True) |
|
||||
logger.info("Message posted. Result: %s", result) |
|
||||
if not result["ok"]: |
|
||||
raise RuntimeError("API Call encountered an error while posting message: "+unicode(result)) |
|
||||
else: |
|
||||
print heading |
|
||||
print "" |
|
||||
print formatted_problems |
|
||||
|
|
||||
|
|
||||
if __name__ == '__main__': |
if __name__ == '__main__': |
||||
parser = argparse.ArgumentParser(prog="Belay", description="Check the security of your Slack.") |
parser = argparse.ArgumentParser(prog="Belay", |
||||
parser.add_argument("-c", "--config", default=None, help="Non-standard location of a configuration file.") |
description="Secure your Slack.") |
||||
parser.add_argument("-f", "--file", default=None, help="File to redirect log output into.") |
parser.add_argument("-c", "--config", default=None, |
||||
parser.add_argument("-t", "--team", default=None, help="Team to check for multi-team configs.") |
help="Non-standard location of a configuration file.") |
||||
parser.add_argument("-v", "--verbose", action="count", default=0, help="Increase log verbosity level. (Default level: WARN, use twice for DEBUG)") |
parser.add_argument("-f", "--file", default=None, |
||||
parser.add_argument("-V", "--version", action="version", version="%(prog)s "+program_version, help="Display version information and exit.") |
help="File to redirect log output into.") |
||||
args = parser.parse_args() |
parser.add_argument("-t", "--team", default=None, |
||||
loglevel = max(10, 30 - (args.verbose * 10)) |
help="Team to check for multi-team configs.") |
||||
logformat = '%(asctime)s %(levelname)s: %(message)s' |
parser.add_argument("-v", "--verbose", action="count", default=0, |
||||
if args.file: |
help="Increase log verbosity level. (Default" + |
||||
logging.basicConfig(filename=args.file, level=loglevel, format=logformat) |
" level: WARN, use twice for DEBUG)") |
||||
else: |
parser.add_argument("-V", "--version", action="version", |
||||
logging.basicConfig(level=loglevel, format=logformat) |
version="%(prog)s " + program_version, |
||||
|
help="Display version information and exit.") |
||||
|
args = parser.parse_args() |
||||
|
loglevel = max(10, 30 - (args.verbose * 10)) |
||||
|
logformat = '%(asctime)s %(levelname)s: %(message)s' |
||||
|
if args.file: |
||||
|
logging.basicConfig(filename=args.file, level=loglevel, format=logformat) |
||||
|
else: |
||||
|
logging.basicConfig(level=loglevel, format=logformat) |
||||
|
|
||||
logger.info("Belaying...") |
logger.info("Belaying...") |
||||
# try: |
try: |
||||
config = load_config(args.config, args.team) |
config = load_config(args.config, args.team) |
||||
belay(config) |
belay(config) |
||||
# except Exception as e: |
except Exception as e: |
||||
# sys.exit(e) |
sys.exit(e) |
||||
# finally: |
finally: |
||||
logging.shutdown() |
logging.shutdown() |
||||
|
|||||
Loading…
Reference in new issue