2016-06-16 01:49:57 +08:00
|
|
|
require 'excon'
|
|
|
|
|
|
|
|
module Jobs
|
|
|
|
class EmitWebHookEvent < Jobs::Base
|
2018-05-21 16:23:09 +08:00
|
|
|
PING_EVENT = 'ping'.freeze
|
2018-05-30 19:27:40 +08:00
|
|
|
MAX_RETRY_COUNT = 4.freeze
|
|
|
|
RETRY_BACKOFF = 5
|
2018-05-21 16:23:09 +08:00
|
|
|
|
2016-06-16 01:49:57 +08:00
|
|
|
def execute(args)
|
2019-04-17 17:03:23 +08:00
|
|
|
memoize_arguments(args)
|
|
|
|
validate_arguments!
|
2016-11-08 11:43:54 +08:00
|
|
|
|
2019-04-17 17:03:23 +08:00
|
|
|
unless ping_event?(arguments[:event_type])
|
|
|
|
validate_argument!(:payload)
|
2016-12-23 00:08:35 +08:00
|
|
|
|
2019-04-17 17:03:23 +08:00
|
|
|
return if webhook_inactive?
|
|
|
|
return if group_webhook_invalid?
|
|
|
|
return if category_webhook_invalid?
|
|
|
|
return if tag_webhook_invalid?
|
2016-11-08 11:43:54 +08:00
|
|
|
end
|
|
|
|
|
2019-04-17 17:03:23 +08:00
|
|
|
send_webhook!
|
2016-06-16 01:49:57 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2019-04-17 17:03:23 +08:00
|
|
|
def validate_arguments!
|
|
|
|
validate_argument!(:web_hook_id)
|
|
|
|
validate_argument!(:event_type)
|
|
|
|
raise Discourse::InvalidParameters.new(:web_hook_id) if web_hook.blank?
|
2016-12-23 00:08:35 +08:00
|
|
|
end
|
|
|
|
|
2019-04-17 17:03:23 +08:00
|
|
|
def validate_argument!(key)
|
|
|
|
raise Discourse::InvalidParameters.new(key) unless arguments[key].present?
|
2016-12-23 00:08:35 +08:00
|
|
|
end
|
|
|
|
|
2019-04-17 17:03:23 +08:00
|
|
|
def memoize_arguments(args)
|
|
|
|
@arguments = args
|
|
|
|
@retry_count = @arguments[:retry_count] || 0
|
2016-12-23 00:08:35 +08:00
|
|
|
end
|
|
|
|
|
2019-04-17 17:03:23 +08:00
|
|
|
def send_webhook!
|
2017-12-11 16:15:50 +08:00
|
|
|
uri = URI(web_hook.payload_url.strip)
|
2016-12-23 00:08:35 +08:00
|
|
|
|
|
|
|
conn = Excon.new(
|
|
|
|
uri.to_s,
|
|
|
|
ssl_verify_peer: web_hook.verify_certificate,
|
|
|
|
retry_limit: 0
|
|
|
|
)
|
2016-06-16 01:49:57 +08:00
|
|
|
|
2019-04-17 17:03:23 +08:00
|
|
|
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)
|
|
|
|
|
2018-07-27 08:19:11 +08:00
|
|
|
response = nil
|
2016-06-16 01:49:57 +08:00
|
|
|
|
|
|
|
begin
|
|
|
|
now = Time.zone.now
|
2019-04-17 17:03:23 +08:00
|
|
|
response = conn.post(headers: web_hook_headers, body: web_hook_body)
|
2016-12-23 00:08:35 +08:00
|
|
|
web_hook_event.update!(
|
2019-04-17 17:03:23 +08:00
|
|
|
headers: MultiJson.dump(web_hook_headers),
|
2016-12-23 00:08:35 +08:00
|
|
|
status: response.status,
|
|
|
|
response_headers: MultiJson.dump(response.headers),
|
|
|
|
response_body: response.body,
|
|
|
|
duration: ((Time.zone.now - now) * 1000).to_i
|
|
|
|
)
|
2019-03-21 21:04:54 +08:00
|
|
|
rescue => e
|
2019-04-15 14:49:48 +08:00
|
|
|
web_hook_event.update!(
|
2019-04-17 17:03:23 +08:00
|
|
|
headers: MultiJson.dump(web_hook_headers),
|
2019-04-15 14:49:48 +08:00
|
|
|
status: -1,
|
|
|
|
response_headers: MultiJson.dump(error: e),
|
|
|
|
duration: ((Time.zone.now - now) * 1000).to_i
|
|
|
|
)
|
2016-06-16 01:49:57 +08:00
|
|
|
end
|
2018-05-30 19:27:40 +08:00
|
|
|
|
2019-04-17 17:03:23 +08:00
|
|
|
publish_webhook_event(web_hook_event)
|
|
|
|
retry_web_hook if response&.status != 200
|
|
|
|
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)
|
|
|
|
Jobs.enqueue_in(delay.minutes, :emit_web_hook_event, arguments)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def publish_webhook_event(web_hook_event)
|
2019-03-21 21:04:54 +08:00
|
|
|
MessageBus.publish("/web_hook_events/#{web_hook.id}", {
|
|
|
|
web_hook_event_id: web_hook_event.id,
|
2019-04-17 17:03:23 +08:00
|
|
|
event_type: arguments[:event_type]
|
2019-03-21 21:04:54 +08:00
|
|
|
}, user_ids: User.human_users.staff.pluck(:id))
|
2019-04-17 17:03:23 +08:00
|
|
|
end
|
2019-03-21 21:04:54 +08:00
|
|
|
|
2019-04-17 17:03:23 +08:00
|
|
|
def ping_event?(event_type)
|
|
|
|
PING_EVENT == event_type
|
2018-05-30 19:27:40 +08:00
|
|
|
end
|
|
|
|
|
2019-04-17 17:03:23 +08:00
|
|
|
def webhook_inactive?
|
|
|
|
!web_hook.active?
|
|
|
|
end
|
|
|
|
|
|
|
|
def group_webhook_invalid?
|
|
|
|
web_hook.group_ids.present? && (arguments[:group_id].present? ||
|
|
|
|
!web_hook.group_ids.include?(arguments[:group_id]))
|
|
|
|
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 arguments
|
|
|
|
@arguments
|
|
|
|
end
|
|
|
|
|
|
|
|
def parsed_payload
|
|
|
|
@parsed_payload ||= JSON.parse(arguments[:payload])
|
|
|
|
end
|
|
|
|
|
|
|
|
def web_hook
|
|
|
|
@web_hook ||= WebHook.find_by(id: arguments[:web_hook_id])
|
|
|
|
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)}"
|
2018-05-30 19:27:40 +08:00
|
|
|
end
|
2019-04-17 17:03:23 +08:00
|
|
|
|
|
|
|
headers
|
2016-06-16 01:49:57 +08:00
|
|
|
end
|
2019-04-17 17:03:23 +08:00
|
|
|
|
|
|
|
def build_webhook_body
|
|
|
|
body = {}
|
|
|
|
|
|
|
|
if ping_event?(arguments[:event_type])
|
|
|
|
body['ping'] = "OK"
|
|
|
|
else
|
|
|
|
body[arguments[:event_type]] = parsed_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_id: web_hook.id, payload: web_hook_body)
|
|
|
|
end
|
|
|
|
|
2016-06-16 01:49:57 +08:00
|
|
|
end
|
|
|
|
end
|