discourse/app/models/web_hook.rb

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

175 lines
5.2 KiB
Ruby
Raw Permalink Normal View History

# frozen_string_literal: true
2016-06-15 13:49:57 -04:00
class WebHook < ActiveRecord::Base
has_and_belongs_to_many :web_hook_event_types
has_and_belongs_to_many :groups
has_and_belongs_to_many :categories
has_and_belongs_to_many :tags
2016-06-15 13:49:57 -04:00
has_many :web_hook_events, dependent: :destroy
has_many :redelivering_webhook_events
FEATURE: Add WebHookEventsDailyAggregate (#27542) * FEATURE: Add WebHookEventsDailyAggregate Add WebHookEventsDailyAggregate model to store daily aggregates of web hook events. Add AggregateWebHooksEvents job to aggregate web hook events daily. Add spec for WebHookEventsDailyAggregate model. * DEV: Update annotations for web_hook_events_daily_aggregate.rb * DEV: Update app/jobs/scheduled/aggregate_web_hooks_events.rb Co-authored-by: Martin Brennan <martin@discourse.org> * DEV: Address review feedback Solves: - https://github.com/discourse/discourse/pull/27542#discussion_r1646961101 - https://github.com/discourse/discourse/pull/27542#discussion_r1646958890 - https://github.com/discourse/discourse/pull/27542#discussion_r1646976808 - https://github.com/discourse/discourse/pull/27542#discussion_r1646979846 - https://github.com/discourse/discourse/pull/27542#discussion_r1646981036 * A11Y: Add translation to retain_web_hook_events_aggregate_days key * FEATURE: Purge old web hook events daily aggregate Solves: https://github.com/discourse/discourse/pull/27542#discussion_r1646961101 * DEV: Update tests for web_hook_events_daily_aggregate Update WebHookEventsDailyAggregate to not use save! at the end Solves: https://github.com/discourse/discourse/pull/27542#discussion_r1646984601 * PERF: Change job query to use WebHook table instead of WebHookEvent table * DEV: Update tests to use `fab!` * DEV: Address code review feedback. Add idempotency to job Add has_many to WebHook * DEV: add test case for job and change job query * DEV: Change AggregateWebHooksEvents job test name --------- Co-authored-by: Martin Brennan <martin@discourse.org>
2024-06-25 12:56:47 -04:00
has_many :web_hook_events_daily_aggregates, dependent: :destroy
2016-06-15 13:49:57 -04:00
default_scope { order("id ASC") }
validates :payload_url, presence: true, format: URI.regexp(%w[http https])
validates :secret, length: { minimum: 12 }, allow_blank: true
validates_presence_of :content_type
validates_presence_of :last_delivery_status
validates_presence_of :web_hook_event_types, unless: :wildcard_web_hook?
validate :ensure_payload_url_allowed, if: :payload_url_changed?
2016-06-15 13:49:57 -04:00
2017-12-11 03:15:50 -05:00
before_save :strip_url
def tag_names=(tag_names_arg)
DiscourseTagging.add_or_create_tags_by_name(self, tag_names_arg, unlimited: true)
end
2016-06-15 13:49:57 -04:00
def self.content_types
@content_types ||= Enum.new("application/json" => 1, "application/x-www-form-urlencoded" => 2)
end
def self.last_delivery_statuses
@last_delivery_statuses ||= Enum.new(inactive: 1, failed: 2, successful: 3, disabled: 4)
end
def self.default_event_types
WebHookEventType.where(
id: [
WebHookEventType::TYPES[:post_created],
WebHookEventType::TYPES[:post_edited],
WebHookEventType::TYPES[:post_destroyed],
WebHookEventType::TYPES[:post_recovered],
],
)
2016-06-15 13:49:57 -04:00
end
def strip_url
self.payload_url = (payload_url || "").strip.presence
end
EVENT_NAME_TO_EVENT_TYPE_MAP = {
/\Atopic_\w+_status_updated\z/ => "topic_edited",
"reviewable_score_updated" => "reviewable_updated",
"reviewable_transitioned_to" => "reviewable_updated",
}
def self.translate_event_name_to_type(event_name)
EVENT_NAME_TO_EVENT_TYPE_MAP.each do |key, value|
if key.is_a?(Regexp)
return value if event_name.to_s =~ key
else
return value if event_name.to_s == key
end
end
event_name.to_s
end
def self.active_web_hooks(event)
event_type = translate_event_name_to_type(event)
2016-06-15 13:49:57 -04:00
WebHook
.where(active: true)
.joins(:web_hook_event_types)
.where("web_hooks.wildcard_web_hook = ? OR web_hook_event_types.name = ?", true, event_type)
.distinct
2016-06-15 13:49:57 -04:00
end
def self.enqueue_hooks(type, event, opts = {})
active_web_hooks(event).each do |web_hook|
Jobs.enqueue(
:emit_web_hook_event,
opts.merge(web_hook_id: web_hook.id, event_name: event.to_s, event_type: type.to_s),
)
2016-06-15 13:49:57 -04:00
end
end
def self.enqueue_object_hooks(type, object, event, serializer = nil, opts = {})
if active_web_hooks(event).exists?
payload = WebHook.generate_payload(type, object, serializer)
WebHook.enqueue_hooks(type, event, opts.merge(id: object.id, payload: payload))
end
2016-06-15 13:49:57 -04:00
end
def self.enqueue_topic_hooks(event, topic, payload = nil)
if active_web_hooks(event).exists? && topic.present?
payload ||=
begin
topic_view = TopicView.new(topic.id, Discourse.system_user, skip_staff_action: true)
WebHook.generate_payload(:topic, topic_view, WebHookTopicViewSerializer)
end
WebHook.enqueue_hooks(
:topic,
event,
id: topic.id,
category_id: topic.category_id,
tag_ids: topic.tags.pluck(:id),
payload: payload,
)
end
end
2017-12-11 03:15:50 -05:00
def self.enqueue_post_hooks(event, post, payload = nil)
if active_web_hooks(event).exists? && post.present?
payload ||= WebHook.generate_payload(:post, post)
WebHook.enqueue_hooks(
:post,
event,
id: post.id,
category_id: post.topic&.category_id,
tag_ids: post.topic&.tags&.pluck(:id),
payload: payload,
)
end
2017-12-11 03:15:50 -05:00
end
def self.generate_payload(type, object, serializer = nil)
serializer ||= TagSerializer if type == :tag
serializer ||= "WebHook#{type.capitalize}Serializer".constantize
serializer.new(object, scope: self.guardian, root: false).to_json
end
private
def self.guardian
Guardian.new(Discourse.system_user)
end
# This check is to improve UX
# IPs are re-checked at request time
def ensure_payload_url_allowed
return if payload_url.blank?
uri = URI(payload_url.strip)
allowed =
begin
FinalDestination::SSRFDetector.lookup_and_filter_ips(uri.hostname).present?
rescue FinalDestination::SSRFDetector::DisallowedIpError
false
end
self.errors.add(:base, I18n.t("webhooks.payload_url.blocked_or_internal")) if !allowed
end
2016-06-15 13:49:57 -04:00
end
# == Schema Information
#
# Table name: web_hooks
#
# id :integer not null, primary key
# payload_url :string not null
# content_type :integer default(1), not null
# last_delivery_status :integer default(1), not null
2016-10-31 05:32:11 -04:00
# status :integer default(1), not null
2016-06-15 13:49:57 -04:00
# secret :string default("")
# wildcard_web_hook :boolean default(FALSE), not null
# verify_certificate :boolean default(TRUE), not null
# active :boolean default(FALSE), not null
2019-01-11 14:29:56 -05:00
# created_at :datetime not null
# updated_at :datetime not null
2016-06-15 13:49:57 -04:00
#