diff --git a/app/assets/javascripts/admin/templates/web-hooks-show.hbs b/app/assets/javascripts/admin/templates/web-hooks-show.hbs index 458a18c1e22..c7a8e030888 100644 --- a/app/assets/javascripts/admin/templates/web-hooks-show.hbs +++ b/app/assets/javascripts/admin/templates/web-hooks-show.hbs @@ -60,6 +60,8 @@ + {{plugin-outlet name="web-hook-fields" args=(hash model=model)}} +
{{input type="checkbox" name="verify_certificate" checked=model.verify_certificate}} {{i18n 'admin.web_hooks.verify_certificate'}}
diff --git a/app/jobs/regular/emit_web_hook_event.rb b/app/jobs/regular/emit_web_hook_event.rb index 2186ca9baa1..043c97214c7 100644 --- a/app/jobs/regular/emit_web_hook_event.rb +++ b/app/jobs/regular/emit_web_hook_event.rb @@ -3,32 +3,24 @@ require 'excon' module Jobs class EmitWebHookEvent < Jobs::Base def execute(args) - raise Discourse::InvalidParameters.new(:web_hook_id) unless args[:web_hook_id].present? - raise Discourse::InvalidParameters.new(:event_type) unless args[:event_type].present? - - args = args.dup - - if args[:topic_id] - args[:topic_view] = TopicView.new(args[:topic_id], Discourse.system_user) + [:web_hook_id, :event_type].each do |key| + raise Discourse::InvalidParameters.new(key) unless args[key].present? end - if args[:post_id] - # deleted post so skip - return unless args[:post] = Post.find_by(id: args[:post_id]) - end + web_hook = WebHook.find_by(id: args[:web_hook_id]) + raise Discourse::InvalidParameters(:web_hook_id) if web_hook.blank? - if args[:user_id] - return unless args[:user] = User.find_by(id: args[:user_id]) - end - - web_hook = WebHook.find(args[:web_hook_id]) - - unless args[:event_type] == 'ping' + unless ping_event?(args[:event_type]) return unless web_hook.active? + return if web_hook.group_ids.present? && (args[:group_id].present? || !web_hook.group_ids.include?(args[:group_id])) + return if web_hook.category_ids.present? && (!args[:category_id].present? || !web_hook.category_ids.include?(args[:category_id])) + + event_type = args[:event_type].to_s + return unless self.send("setup_#{event_type}") end web_hook_request(args, web_hook) @@ -36,12 +28,56 @@ module Jobs private - def web_hook_request(args, web_hook) + def guardian + Guardian.new(Discourse.system_user) + end + def setup_post(args) + post = Post.find_by(id: args[:post_id]) + return if post.blank? + args[:payload] = WebHookPostSerializer.new(post, scope: guardian, root: false).as_json + end + + def setup_topic(args) + topic_view = (TopicView.new(args[:topic_id], Discourse.system_user) rescue nil) + return if topic_view.blank? + args[:payload] = WebHookTopicViewSerializer.new(post, scope: guardian, root: false).as_json + end + + def setup_user(args) + user = User.find_by(id: args[:user_id]) + return if user.blank? + args[:payload] = WebHookUserSerializer.new(post, scope: guardian, root: false).as_json + end + + def ping_event?(event_type) + event_type.to_s == 'ping'.freeze + end + + def build_web_hook_body(args, web_hook) + body = {} + guardian = Guardian.new(Discourse.system_user) + event_type = args[:event_type].to_s + + if ping_event?(event_type) + body[:ping] = 'OK' + else + body[event_type] = args[:payload] + end + + new_body = Plugin::Filter.apply(:after_build_web_hook_body, self, body) + + MultiJson.dump(new_body) + end + + def web_hook_request(args, web_hook) uri = URI(web_hook.payload_url) - conn = Excon.new(uri.to_s, - ssl_verify_peer: web_hook.verify_certificate, - retry_limit: 0) + + conn = Excon.new( + uri.to_s, + ssl_verify_peer: web_hook.verify_certificate, + retry_limit: 0 + ) body = build_web_hook_body(args, web_hook) web_hook_event = WebHookEvent.create!(web_hook_id: web_hook.id) @@ -53,6 +89,7 @@ module Jobs else 'application/json' end + headers = { 'Accept' => '*/*', 'Connection' => 'close', @@ -64,6 +101,7 @@ module Jobs 'X-Discourse-Event-Id' => web_hook_event.id, 'X-Discourse-Event-Type' => args[:event_type] } + headers['X-Discourse-Event'] = args[:event_name].to_s if args[:event_name].present? if web_hook.secret.present? @@ -72,45 +110,23 @@ module Jobs now = Time.zone.now response = conn.post(headers: headers, body: body) + + web_hook_event.update!( + headers: MultiJson.dump(headers), + payload: body, + status: response.status, + response_headers: MultiJson.dump(response.headers), + response_body: response.body, + duration: ((Time.zone.now - now) * 1000).to_i + ) + + MessageBus.publish("/web_hook_events/#{web_hook.id}", { + web_hook_event_id: web_hook_event.id, + event_type: args[:event_type] + }, user_ids: User.human_users.staff.pluck(:id)) rescue web_hook_event.destroy! end - - web_hook_event.update_attributes!(headers: MultiJson.dump(headers), - payload: body, - status: response.status, - response_headers: MultiJson.dump(response.headers), - response_body: response.body, - duration: ((Time.zone.now - now) * 1000).to_i) - MessageBus.publish("/web_hook_events/#{web_hook.id}", { - web_hook_event_id: web_hook_event.id, - event_type: args[:event_type] - }, user_ids: User.staff.pluck(:id)) end - - def build_web_hook_body(args, web_hook) - body = {} - guardian = Guardian.new(Discourse.system_user) - - if topic_view = args[:topic_view] - body[:topic] = TopicViewSerializer.new(topic_view, scope: guardian, root: false).as_json - end - - if post = args[:post] - body[:post] = PostSerializer.new(post, scope: guardian, root: false).as_json - end - - if user = args[:user] - body[:user] = UserSerializer.new(user, scope: guardian, root: false).as_json - end - - body[:ping] = 'OK' if args[:event_type] == 'ping' - - raise Discourse::InvalidParameters.new if body.empty? - - MultiJson.dump(body) - end - end - end diff --git a/app/models/web_hook.rb b/app/models/web_hook.rb index 4eed81e6bef..dc308736d2c 100644 --- a/app/models/web_hook.rb +++ b/app/models/web_hook.rb @@ -41,11 +41,11 @@ class WebHook < ActiveRecord::Base end def self.enqueue_topic_hooks(event, topic, user=nil) - WebHook.enqueue_hooks(:topic, topic_id: topic.id, user_id: user&.id, category_id: topic&.category_id, event_name: event.to_s) + WebHook.enqueue_hooks(:topic, topic_id: topic.id, category_id: topic&.category_id, event_name: event.to_s) end def self.enqueue_post_hooks(event, post, user=nil) - WebHook.enqueue_hooks(:post, post_id: post.id, topic_id: post&.topic_id, user_id: user&.id, category_id: post&.topic&.category_id, event_name: event.to_s) + WebHook.enqueue_hooks(:post, post_id: post.id, category_id: post&.topic&.category_id, event_name: event.to_s) end %i(topic_destroyed topic_recovered).each do |event| diff --git a/app/serializers/web_hook_post_serializer.rb b/app/serializers/web_hook_post_serializer.rb new file mode 100644 index 00000000000..221c398fa79 --- /dev/null +++ b/app/serializers/web_hook_post_serializer.rb @@ -0,0 +1,17 @@ +class WebHookPostSerializer < PostSerializer + def include_can_edit? + false + end + + def can_delete + false + end + + def can_recover + false + end + + def can_wiki + false + end +end diff --git a/app/serializers/web_hook_topic_view_serializer.rb b/app/serializers/web_hook_topic_view_serializer.rb new file mode 100644 index 00000000000..75c99aeb14a --- /dev/null +++ b/app/serializers/web_hook_topic_view_serializer.rb @@ -0,0 +1,11 @@ +require_dependency 'pinned_check' + +class WebHookTopicViewSerializer < TopicViewSerializer + def include_post_stream? + false + end + + def include_timeline_lookup? + false + end +end diff --git a/app/serializers/web_hook_user_serializer.rb b/app/serializers/web_hook_user_serializer.rb new file mode 100644 index 00000000000..9e6ad411cae --- /dev/null +++ b/app/serializers/web_hook_user_serializer.rb @@ -0,0 +1,5 @@ +class WebHookUserSerializer < UserSerializer + # remove staff attributes + def staff_attributes(*attrs) + end +end diff --git a/spec/jobs/emit_web_hook_event_spec.rb b/spec/jobs/emit_web_hook_event_spec.rb index c01ecb65691..b8e956c6503 100644 --- a/spec/jobs/emit_web_hook_event_spec.rb +++ b/spec/jobs/emit_web_hook_event_spec.rb @@ -10,14 +10,10 @@ describe Jobs::EmitWebHookEvent do expect { subject.execute(event_type: 'post') }.to raise_error(Discourse::InvalidParameters) end - it 'raises an error when there is no event name' do + it 'raises an error when there is no event type' do expect { subject.execute(web_hook_id: 1) }.to raise_error(Discourse::InvalidParameters) end - it 'raises an error when event name is invalid' do - expect { subject.execute(web_hook_id: post_hook.id, event_type: 'post_random') }.to raise_error(Discourse::InvalidParameters) - end - it "doesn't emit when the hook is inactive" do Jobs::EmitWebHookEvent.any_instance.expects(:web_hook_request).never subject.execute(web_hook_id: inactive_hook.id, event_type: 'post', post_id: post.id) diff --git a/spec/models/web_hook_event_type_spec.rb b/spec/models/web_hook_event_type_spec.rb deleted file mode 100644 index bd6b8ef41bb..00000000000 --- a/spec/models/web_hook_event_type_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'rails_helper' - -describe WebHookEventType do - it { is_expected.to validate_presence_of :name } -end