mirror of
https://github.com/discourse/discourse.git
synced 2024-12-01 03:13:38 +08:00
75e159f0ed
* FEATURE: add support for like webhooks Add support for like webhooks. Webhook events only send on user membership in the defined webhook group filters. This also fixes group webhook events, as before this was never used, and the logic was not correct.
181 lines
5.2 KiB
Ruby
181 lines
5.2 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'excon'
|
|
|
|
module Jobs
|
|
class EmitWebHookEvent < ::Jobs::Base
|
|
PING_EVENT = 'ping'
|
|
MAX_RETRY_COUNT = 4
|
|
RETRY_BACKOFF = 5
|
|
|
|
def execute(args)
|
|
@arguments = args
|
|
@retry_count = args[:retry_count] || 0
|
|
@web_hook = WebHook.find_by(id: @arguments[:web_hook_id])
|
|
validate_arguments!
|
|
|
|
return if @web_hook.blank? # Web Hook was deleted
|
|
|
|
unless ping_event?(@arguments[:event_type])
|
|
validate_argument!(:payload)
|
|
|
|
return if webhook_inactive?
|
|
return if group_webhook_invalid?
|
|
return if category_webhook_invalid?
|
|
return if tag_webhook_invalid?
|
|
end
|
|
|
|
send_webhook!
|
|
end
|
|
|
|
private
|
|
|
|
def validate_arguments!
|
|
validate_argument!(:web_hook_id)
|
|
validate_argument!(:event_type)
|
|
end
|
|
|
|
def validate_argument!(key)
|
|
raise Discourse::InvalidParameters.new(key) unless @arguments[key].present?
|
|
end
|
|
|
|
def send_webhook!
|
|
uri = URI(@web_hook.payload_url.strip)
|
|
conn = Excon.new(uri.to_s, ssl_verify_peer: @web_hook.verify_certificate, retry_limit: 0)
|
|
|
|
web_hook_body = build_webhook_body
|
|
web_hook_event = create_webhook_event(web_hook_body)
|
|
web_hook_headers = build_webhook_headers(uri, web_hook_body, web_hook_event)
|
|
web_hook_response = nil
|
|
|
|
begin
|
|
now = Time.zone.now
|
|
web_hook_response = conn.post(headers: web_hook_headers, body: web_hook_body)
|
|
web_hook_event.update!(
|
|
headers: MultiJson.dump(web_hook_headers),
|
|
status: web_hook_response.status,
|
|
response_headers: MultiJson.dump(web_hook_response.headers),
|
|
response_body: web_hook_response.body,
|
|
duration: ((Time.zone.now - now) * 1000).to_i
|
|
)
|
|
rescue => e
|
|
web_hook_event.update!(
|
|
headers: MultiJson.dump(web_hook_headers),
|
|
status: -1,
|
|
response_headers: MultiJson.dump(error: e),
|
|
duration: ((Time.zone.now - now) * 1000).to_i
|
|
)
|
|
end
|
|
|
|
publish_webhook_event(web_hook_event)
|
|
process_webhook_response(web_hook_response)
|
|
end
|
|
|
|
def process_webhook_response(web_hook_response)
|
|
return if web_hook_response&.status.blank?
|
|
|
|
case web_hook_response.status
|
|
when 200..299
|
|
when 404, 410
|
|
if @retry_count >= MAX_RETRY_COUNT
|
|
@web_hook.update!(active: false)
|
|
|
|
StaffActionLogger
|
|
.new(Discourse.system_user)
|
|
.log_web_hook_deactivate(@web_hook, web_hook_response.status)
|
|
end
|
|
else
|
|
retry_web_hook
|
|
end
|
|
end
|
|
|
|
def retry_web_hook
|
|
if SiteSetting.retry_web_hook_events?
|
|
@retry_count += 1
|
|
return if @retry_count > MAX_RETRY_COUNT
|
|
delay = RETRY_BACKOFF**(@retry_count - 1)
|
|
@arguments[:retry_count] = @retry_count
|
|
::Jobs.enqueue_in(delay.minutes, :emit_web_hook_event, @arguments)
|
|
end
|
|
end
|
|
|
|
def publish_webhook_event(web_hook_event)
|
|
MessageBus.publish("/web_hook_events/#{@web_hook.id}", {
|
|
web_hook_event_id: web_hook_event.id,
|
|
event_type: @arguments[:event_type]
|
|
}, group_ids: [Group::AUTO_GROUPS[:staff]])
|
|
end
|
|
|
|
def ping_event?(event_type)
|
|
PING_EVENT == event_type
|
|
end
|
|
|
|
def webhook_inactive?
|
|
!@web_hook.active?
|
|
end
|
|
|
|
def group_webhook_invalid?
|
|
@web_hook.group_ids.present? && (@arguments[:group_ids].blank? ||
|
|
(@web_hook.group_ids & @arguments[:group_ids]).blank?)
|
|
end
|
|
|
|
def category_webhook_invalid?
|
|
@web_hook.category_ids.present? && (!@arguments[:category_id].present? ||
|
|
!@web_hook.category_ids.include?(@arguments[:category_id]))
|
|
end
|
|
|
|
def tag_webhook_invalid?
|
|
@web_hook.tag_ids.present? && (@arguments[:tag_ids].blank? ||
|
|
(@web_hook.tag_ids & @arguments[:tag_ids]).blank?)
|
|
end
|
|
|
|
def build_webhook_headers(uri, web_hook_body, web_hook_event)
|
|
content_type =
|
|
case @web_hook.content_type
|
|
when WebHook.content_types['application/x-www-form-urlencoded']
|
|
'application/x-www-form-urlencoded'
|
|
else
|
|
'application/json'
|
|
end
|
|
|
|
headers = {
|
|
'Accept' => '*/*',
|
|
'Connection' => 'close',
|
|
'Content-Length' => web_hook_body.bytesize,
|
|
'Content-Type' => content_type,
|
|
'Host' => uri.host,
|
|
'User-Agent' => "Discourse/#{Discourse::VERSION::STRING}",
|
|
'X-Discourse-Instance' => Discourse.base_url,
|
|
'X-Discourse-Event-Id' => web_hook_event.id,
|
|
'X-Discourse-Event-Type' => @arguments[:event_type]
|
|
}
|
|
|
|
headers['X-Discourse-Event'] = @arguments[:event_name] if @arguments[:event_name].present?
|
|
|
|
if @web_hook.secret.present?
|
|
headers['X-Discourse-Event-Signature'] = "sha256=#{OpenSSL::HMAC.hexdigest("sha256", @web_hook.secret, web_hook_body)}"
|
|
end
|
|
|
|
headers
|
|
end
|
|
|
|
def build_webhook_body
|
|
body = {}
|
|
|
|
if ping_event?(@arguments[:event_type])
|
|
body['ping'] = "OK"
|
|
else
|
|
body[@arguments[:event_type]] = JSON.parse(@arguments[:payload])
|
|
end
|
|
|
|
new_body = Plugin::Filter.apply(:after_build_web_hook_body, self, body)
|
|
MultiJson.dump(new_body)
|
|
end
|
|
|
|
def create_webhook_event(web_hook_body)
|
|
WebHookEvent.create!(web_hook: @web_hook, payload: web_hook_body)
|
|
end
|
|
|
|
end
|
|
end
|