2022-11-02 09:41:30 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
class Chat::IncomingChatWebhooksController < ApplicationController
|
|
|
|
WEBHOOK_MESSAGES_PER_MINUTE_LIMIT = 10
|
|
|
|
|
|
|
|
skip_before_action :verify_authenticity_token, :redirect_to_login_if_required
|
|
|
|
|
|
|
|
before_action :validate_payload
|
|
|
|
|
|
|
|
def create_message
|
|
|
|
debug_payload
|
|
|
|
|
|
|
|
process_webhook_payload(text: params[:text], key: params[:key])
|
|
|
|
end
|
|
|
|
|
|
|
|
# See https://api.slack.com/reference/messaging/payload for the
|
|
|
|
# slack message payload format. For now we only support the
|
|
|
|
# text param, which we preprocess lightly to remove the slack-isms
|
|
|
|
# in the formatting.
|
|
|
|
def create_message_slack_compatible
|
|
|
|
debug_payload
|
|
|
|
|
|
|
|
# See note in validate_payload on why this is needed
|
|
|
|
attachments =
|
|
|
|
if params[:payload].present?
|
|
|
|
payload = params[:payload]
|
|
|
|
if String === payload
|
|
|
|
payload = JSON.parse(payload)
|
|
|
|
payload.deep_symbolize_keys!
|
|
|
|
end
|
|
|
|
payload[:attachments]
|
|
|
|
else
|
|
|
|
params[:attachments]
|
|
|
|
end
|
|
|
|
|
|
|
|
if params[:text].present?
|
|
|
|
text = Chat::SlackCompatibility.process_text(params[:text])
|
|
|
|
else
|
|
|
|
text = Chat::SlackCompatibility.process_legacy_attachments(attachments)
|
|
|
|
end
|
|
|
|
|
|
|
|
process_webhook_payload(text: text, key: params[:key])
|
|
|
|
rescue JSON::ParserError
|
|
|
|
raise Discourse::InvalidParameters
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def process_webhook_payload(text:, key:)
|
|
|
|
validate_message_length(text)
|
|
|
|
webhook = find_and_rate_limit_webhook(key)
|
|
|
|
|
|
|
|
chat_message_creator =
|
|
|
|
Chat::ChatMessageCreator.create(
|
|
|
|
chat_channel: webhook.chat_channel,
|
|
|
|
user: Discourse.system_user,
|
|
|
|
content: text,
|
|
|
|
incoming_chat_webhook: webhook,
|
|
|
|
)
|
|
|
|
if chat_message_creator.failed?
|
|
|
|
render_json_error(chat_message_creator.error)
|
|
|
|
else
|
|
|
|
render json: success_json
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def find_and_rate_limit_webhook(key)
|
|
|
|
webhook = IncomingChatWebhook.includes(:chat_channel).find_by(key: key)
|
|
|
|
raise Discourse::NotFound unless webhook
|
|
|
|
|
|
|
|
# Rate limit to 10 messages per-minute. We can move to a site setting in the future if needed.
|
|
|
|
RateLimiter.new(
|
|
|
|
nil,
|
|
|
|
"incoming_chat_webhook_#{webhook.id}",
|
|
|
|
WEBHOOK_MESSAGES_PER_MINUTE_LIMIT,
|
|
|
|
1.minute,
|
|
|
|
).performed!
|
|
|
|
webhook
|
|
|
|
end
|
|
|
|
|
|
|
|
def validate_message_length(message)
|
2022-11-27 19:48:30 -05:00
|
|
|
return if message.length <= SiteSetting.chat_maximum_message_length
|
2022-11-02 09:41:30 -04:00
|
|
|
raise Discourse::InvalidParameters.new(
|
2022-11-27 19:48:30 -05:00
|
|
|
"Body cannot be over #{SiteSetting.chat_maximum_message_length} characters",
|
2022-11-02 09:41:30 -04:00
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2023-01-11 20:45:53 -05:00
|
|
|
# The webhook POST body can be in 3 different formats:
|
|
|
|
#
|
|
|
|
# * { text: "message text" }, which is the most basic method, and also mirrors Slack payloads
|
|
|
|
# * { attachments: [ text: "message text" ] }, which is a variant of Slack payloads using legacy attachments
|
|
|
|
# * { payload: "<JSON STRING>", attachments: null, text: null }, where JSON STRING can look
|
|
|
|
# like the `attachments` example above (along with other attributes), which is fired by OpsGenie
|
2022-11-02 09:41:30 -04:00
|
|
|
def validate_payload
|
2023-01-11 20:45:53 -05:00
|
|
|
params.require(:key)
|
2022-11-02 09:41:30 -04:00
|
|
|
|
|
|
|
if !params[:text] && !params[:payload] && !params[:attachments]
|
|
|
|
raise Discourse::InvalidParameters
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def debug_payload
|
|
|
|
return if !SiteSetting.chat_debug_webhook_payloads
|
|
|
|
Rails.logger.warn(
|
2023-01-11 20:45:53 -05:00
|
|
|
"Debugging chat webhook payload for endpoint #{params[:key]}: " +
|
2022-11-02 09:41:30 -04:00
|
|
|
JSON.dump(
|
|
|
|
{ payload: params[:payload], attachments: params[:attachments], text: params[:text] },
|
|
|
|
),
|
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|