Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 70 additions & 59 deletions src/app/bin/slack.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ def build_slack_message(payload):
params['icon_url'] = config.get('from_user_icon')

channel = config.get('channel')

if channel:
params['channel'] = channel
else:
Expand All @@ -103,8 +104,13 @@ def send_slack_message(payload):
try:
req = {}
config = payload.get('configuration')
body = json.dumps(build_slack_message(payload))

channels_array = []

# Get channel string value from config
channel_str = config.get("channel")
# Convert channel str into an array to check for multiple channels - replace all whitespaces and remove a possible comma at the end of the string
channels_array = channel_str.replace(" ", "").rstrip(',').split(",")

http_proxy = config.get('http_proxy', '')

if config.get('proxy_url_override'):
Expand All @@ -116,66 +122,71 @@ def send_slack_message(payload):
proxy_handler = req.ProxyHandler({ 'http': "%s" % http_proxy, 'https': "%s" % http_proxy})
opener = req.build_opener(proxy_handler)
req.install_opener(opener)

# Since Slack webhook URLs are deprecated, we will bias towards using Slack Apps, if they are provided
is_using_slack_app = (("slack_app_oauth_token" in config and config["slack_app_oauth_token"]) or ("slack_app_oauth_token_override" in config and config["slack_app_oauth_token_override"]))
if is_using_slack_app:
token = config.get("slack_app_oauth_token", "")
if config.get("slack_app_oauth_token_override"):
token = config.get("slack_app_oauth_token_override")
log("INFO Using Slack App OAuth token from slack_app_oauth_token_override: %s" % token)

for channel in channels_array:
# replace original channel value with the current one from the loop
payload["configuration"]["channel"] = channel
body = json.dumps(build_slack_message(payload))

# Since Slack webhook URLs are deprecated, we will bias towards using Slack Apps, if they are provided
is_using_slack_app = (("slack_app_oauth_token" in config and config["slack_app_oauth_token"]) or ("slack_app_oauth_token_override" in config and config["slack_app_oauth_token_override"]))
if is_using_slack_app:
token = config.get("slack_app_oauth_token", "")
if config.get("slack_app_oauth_token_override"):
token = config.get("slack_app_oauth_token_override")
log("INFO Using Slack App OAuth token from slack_app_oauth_token_override: %s" % token)
else:
log("INFO Using configured Slack App OAuth token: %s" % token)

log('DEBUG Calling url="https://slack.com/api/chat.postMessage" with token=%s and body=%s' % (token, body))
msg_req = req.Request("https://slack.com/api/chat.postMessage", ensure_binary(body), {"Content-Type": "application/json", 'Authorization': "Bearer %s" % token})
# To preserve backwards compatibility, we will fallback to the webhook_url configuration, if a Slack App OAuth token is not provided
else:
log("INFO Using configured Slack App OAuth token: %s" % token)

log('DEBUG Calling url="https://slack.com/api/chat.postMessage" with token=%s and body=%s' % (token, body))
msg_req = req.Request("https://slack.com/api/chat.postMessage", ensure_binary(body), {"Content-Type": "application/json", 'Authorization': "Bearer %s" % token})
url = config.get('webhook_url', '')

if config.get('webhook_url_override'):
url = config.get('webhook_url_override', '')
log("INFO Using webhook URL from webhook_url_override: %s" % url)
elif not url:
log("FATAL No webhook URL configured and no override specified")
return ERROR_CODE_VALIDATION_FAILED
else:
log("INFO Using configured webhook URL: %s" % url)

if not url.startswith('https:'):
log("FATAL Invalid webhook URL specified. The URL must use HTTPS.")
return ERROR_CODE_VALIDATION_FAILED

log('DEBUG Calling url="%s" with body=%s' % (url, body))
msg_req = req.Request(url, ensure_binary(body), {"Content-Type": "application/json"})

# To preserve backwards compatibility, we will fallback to the webhook_url configuration, if a Slack App OAuth token is not provided
else:
url = config.get('webhook_url', '')
if config.get('webhook_url_override'):
url = config.get('webhook_url_override', '')
log("INFO Using webhook URL from webhook_url_override: %s" % url)
elif not url:
log("FATAL No webhook URL configured and no override specified")
return ERROR_CODE_VALIDATION_FAILED
else:
log("INFO Using configured webhook URL: %s" % url)

if not url.startswith('https:'):
log("FATAL Invalid webhook URL specified. The URL must use HTTPS.")
return ERROR_CODE_VALIDATION_FAILED

log('DEBUG Calling url="%s" with body=%s' % (url, body))
msg_req = req.Request(url, ensure_binary(body), {"Content-Type": "application/json"})

try:
res = urllib.request.urlopen(msg_req)
res_body = str(res.read())
log("INFO Slack API responded with HTTP status=%d" % res.code)
log("DEBUG Slack API response: %s" % res_body)
if 200 <= res.code < 300:
if "invalid_auth" in res_body:
log("FATAL The Slack App OAuth token provided is invalid or does not have the permission to post messages to the channel provided.")
return ERROR_CODE_FORBIDDEN
if "channel_not_found" in res_body or "channel_is_archived" in res_body:
log("FATAL The channel provided was not found or is archived. If the channel is private, please make sure the Slack App is added to the channel.")
return ERROR_CODE_CHANNEL_NOT_FOUND
if "error" in res_body:
return ERROR_CODE_UNKNOWN
return OK
except urllib.error.HTTPError as e:
log("ERROR HTTP request to Slack API failed: %s" % e)
try:
res = e.read()
log("ERROR Slack error response: %s" % res)
if res in ('channel_not_found', 'channel_is_archived'):
return ERROR_CODE_CHANNEL_NOT_FOUND
if res == 'action_prohibited':
return ERROR_CODE_FORBIDDEN
except:
pass
return ERROR_CODE_HTTP_FAIL
res = urllib.request.urlopen(msg_req)
res_body = str(res.read())
log("INFO Slack API responded with HTTP status=%d" % res.code)
log("DEBUG Slack API response: %s" % res_body)
if 200 <= res.code < 300:
if "invalid_auth" in res_body:
log("FATAL The Slack App OAuth token provided is invalid or does not have the permission to post messages to the channel provided.")
return ERROR_CODE_FORBIDDEN
if "channel_not_found" in res_body or "channel_is_archived" in res_body:
log("FATAL The channel provided was not found or is archived. If the channel is private, please make sure the Slack App is added to the channel.")
return ERROR_CODE_CHANNEL_NOT_FOUND
if "error" in res_body:
return ERROR_CODE_UNKNOWN
except urllib.error.HTTPError as e:
log("ERROR HTTP request to Slack API failed: %s" % e)
try:
res = e.read()
log("ERROR Slack error response: %s" % res)
if res in ('channel_not_found', 'channel_is_archived'):
return ERROR_CODE_CHANNEL_NOT_FOUND
if res == 'action_prohibited':
return ERROR_CODE_FORBIDDEN
except:
pass
return ERROR_CODE_HTTP_FAIL
return OK
except:
log("FATAL Unexpected error:", sys.exc_info()[0])
track = traceback.format_exc()
Expand Down
2 changes: 1 addition & 1 deletion src/app/default/data/ui/alerts/slack.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

<div class="controls">
<input type="text" name="action.slack.param.channel" id="slack_channel" />
<span class="help-block">Slack channel to send the message to (Should start with # or @)</span>
<span class="help-block">Slack channel to send the message to (Should start with # or @). To send the message to multiple channels, enter them as comma-separated values.</span>
</div>
</div>
<br clear="both" />
Expand Down
Loading