summaryrefslogtreecommitdiff
path: root/app/models/concerns
diff options
context:
space:
mode:
Diffstat (limited to 'app/models/concerns')
-rw-r--r--app/models/concerns/analytics/cycle_analytics/stageable.rb5
-rw-r--r--app/models/concerns/ci/has_variable.rb11
-rw-r--r--app/models/concerns/ci/maskable.rb20
-rw-r--r--app/models/concerns/ci/metadatable.rb4
-rw-r--r--app/models/concerns/commit_signature.rb4
-rw-r--r--app/models/concerns/counter_attribute.rb17
-rw-r--r--app/models/concerns/cross_database_modification.rb4
-rw-r--r--app/models/concerns/enums/package_metadata.rb20
-rw-r--r--app/models/concerns/exportable.rb50
-rw-r--r--app/models/concerns/group_descendant.rb2
-rw-r--r--app/models/concerns/id_in_ordered.rb2
-rw-r--r--app/models/concerns/integrations/has_web_hook.rb4
-rw-r--r--app/models/concerns/issuable_link.rb6
-rw-r--r--app/models/concerns/issue_parent.rb11
-rw-r--r--app/models/concerns/noteable.rb19
-rw-r--r--app/models/concerns/prometheus_adapter.rb4
-rw-r--r--app/models/concerns/reactive_caching.rb8
-rw-r--r--app/models/concerns/require_email_verification.rb7
-rw-r--r--app/models/concerns/sensitive_serializable_hash.rb8
-rw-r--r--app/models/concerns/sha_attribute.rb4
-rw-r--r--app/models/concerns/spammable.rb2
-rw-r--r--app/models/concerns/taskable.rb26
-rw-r--r--app/models/concerns/token_authenticatable.rb2
-rw-r--r--app/models/concerns/vulnerability_finding_helpers.rb2
-rw-r--r--app/models/concerns/web_hooks/auto_disabling.rb69
-rw-r--r--app/models/concerns/web_hooks/has_web_hooks.rb46
-rw-r--r--app/models/concerns/web_hooks/unstoppable.rb29
-rw-r--r--app/models/concerns/work_item_resource_event.rb12
-rw-r--r--app/models/concerns/x509_serial_number_attribute.rb2
29 files changed, 354 insertions, 46 deletions
diff --git a/app/models/concerns/analytics/cycle_analytics/stageable.rb b/app/models/concerns/analytics/cycle_analytics/stageable.rb
index d1f948d1366..caac4f31e1a 100644
--- a/app/models/concerns/analytics/cycle_analytics/stageable.rb
+++ b/app/models/concerns/analytics/cycle_analytics/stageable.rb
@@ -4,7 +4,6 @@ module Analytics
module CycleAnalytics
module Stageable
extend ActiveSupport::Concern
- include RelativePositioning
include Gitlab::Utils::StrongMemoize
included do
@@ -92,10 +91,6 @@ module Analytics
end_event_identifier.to_s.eql?(stage_params[:end_event_identifier].to_s)
end
- def find_with_same_parent!(id)
- parent.cycle_analytics_stages.find(id)
- end
-
private
def validate_stage_event_pairs
diff --git a/app/models/concerns/ci/has_variable.rb b/app/models/concerns/ci/has_variable.rb
index 3b437fbba16..77e7e5035a0 100644
--- a/app/models/concerns/ci/has_variable.rb
+++ b/app/models/concerns/ci/has_variable.rb
@@ -18,6 +18,7 @@ module Ci
scope :by_key, -> (key) { where(key: key) }
scope :order_key_asc, -> { reorder(key: :asc) }
+ scope :order_key_desc, -> { reorder(key: :desc) }
attr_encrypted :value,
mode: :per_attribute_iv_and_salt,
@@ -30,6 +31,16 @@ module Ci
end
end
+ class_methods do
+ def order_by(method)
+ case method.to_s
+ when 'key_asc' then order_key_asc
+ when 'key_desc' then order_key_desc
+ else all
+ end
+ end
+ end
+
def to_runner_variable
var_cache_key = to_runner_variable_cache_key
diff --git a/app/models/concerns/ci/maskable.rb b/app/models/concerns/ci/maskable.rb
index 62be0150ee0..e2cef0981d1 100644
--- a/app/models/concerns/ci/maskable.rb
+++ b/app/models/concerns/ci/maskable.rb
@@ -12,10 +12,28 @@ module Ci
# * Characters must be from the Base64 alphabet (RFC4648) with the addition of '@', ':', '.', and '~'
# * Absolutely no fun is allowed
REGEX = %r{\A[a-zA-Z0-9_+=/@:.~-]{8,}\z}.freeze
+ # * Single line
+ # * No spaces
+ # * Minimal length of 8 characters
+ # * Some fun is allowed
+ MASK_AND_RAW_REGEX = %r{\A\S{8,}\z}.freeze
included do
validates :masked, inclusion: { in: [true, false] }
- validates :value, format: { with: REGEX }, if: :masked?
+ validates :value, format: { with: REGEX }, if: :masked_and_expanded?
+ validates :value, format: { with: MASK_AND_RAW_REGEX }, if: :masked_and_raw?
+ end
+
+ def masked_and_raw?
+ return false unless self.class.method_defined?(:raw)
+
+ masked? && raw?
+ end
+
+ def masked_and_expanded?
+ return masked? unless self.class.method_defined?(:raw)
+
+ masked? && !raw?
end
def to_runner_variable
diff --git a/app/models/concerns/ci/metadatable.rb b/app/models/concerns/ci/metadatable.rb
index d93f4a150d5..d91f33452a0 100644
--- a/app/models/concerns/ci/metadatable.rb
+++ b/app/models/concerns/ci/metadatable.rb
@@ -22,7 +22,7 @@ module Ci
delegate :set_cancel_gracefully, to: :metadata, prefix: false, allow_nil: false
delegate :id_tokens, to: :metadata, allow_nil: true
- before_create :ensure_metadata
+ before_validation :ensure_metadata, on: :create
end
def has_exposed_artifacts?
@@ -34,7 +34,7 @@ module Ci
end
def ensure_metadata
- metadata || build_metadata(project: project, partition_id: partition_id)
+ metadata || build_metadata(project: project)
end
def degenerated?
diff --git a/app/models/concerns/commit_signature.rb b/app/models/concerns/commit_signature.rb
index 7f1fbbefd94..5dac3c7833a 100644
--- a/app/models/concerns/commit_signature.rb
+++ b/app/models/concerns/commit_signature.rb
@@ -4,6 +4,7 @@ module CommitSignature
included do
include ShaAttribute
+ include EachBatch
sha_attribute :commit_sha
@@ -14,7 +15,8 @@ module CommitSignature
other_user: 3,
unverified_key: 4,
unknown_key: 5,
- multiple_signatures: 6
+ multiple_signatures: 6,
+ revoked_key: 7
}
belongs_to :project, class_name: 'Project', foreign_key: 'project_id', optional: false
diff --git a/app/models/concerns/counter_attribute.rb b/app/models/concerns/counter_attribute.rb
index 784afd1f231..58ea57962c5 100644
--- a/app/models/concerns/counter_attribute.rb
+++ b/app/models/concerns/counter_attribute.rb
@@ -93,7 +93,7 @@ module CounterAttribute
run_after_commit_or_now do
new_value = counter(attribute).increment(increment)
- log_increment_counter(attribute, increment.amount, new_value)
+ log_increment_counter(attribute, increment, new_value)
end
end
@@ -101,7 +101,7 @@ module CounterAttribute
run_after_commit_or_now do
new_value = counter(attribute).bulk_increment(increments)
- log_increment_counter(attribute, increments.sum(&:amount), new_value)
+ log_bulk_increment_counter(attribute, increments, new_value)
end
end
@@ -198,7 +198,8 @@ module CounterAttribute
message: 'Increment counter attribute',
attribute: attribute,
project_id: project_id,
- increment: increment,
+ increment: increment.amount,
+ ref: increment.ref,
new_counter_value: new_value,
current_db_value: read_attribute(attribute)
)
@@ -206,6 +207,16 @@ module CounterAttribute
Gitlab::AppLogger.info(payload)
end
+ def log_bulk_increment_counter(attribute, increments, new_value)
+ if Feature.enabled?(:split_log_bulk_increment_counter, type: :ops)
+ increments.each do |increment|
+ log_increment_counter(attribute, increment, new_value)
+ end
+ else
+ log_increment_counter(attribute, Gitlab::Counters::Increment.new(amount: increments.sum(&:amount)), new_value)
+ end
+ end
+
def log_clear_counter(attribute)
payload = Gitlab::ApplicationContext.current.merge(
message: 'Clear counter attribute',
diff --git a/app/models/concerns/cross_database_modification.rb b/app/models/concerns/cross_database_modification.rb
index 273d5f35e76..df4f4f0bfe1 100644
--- a/app/models/concerns/cross_database_modification.rb
+++ b/app/models/concerns/cross_database_modification.rb
@@ -102,6 +102,10 @@ module CrossDatabaseModification
:gitlab_main
when 'Ci::ApplicationRecord'
:gitlab_ci
+ when 'MainClusterwide::ApplicationRecord'
+ :gitlab_main_clusterwide
+ when 'PackageMetadata::ApplicationRecord'
+ :gitlab_pm
else
Gitlab::Database::GitlabSchema.table_schema(table_name) if table_name
end
diff --git a/app/models/concerns/enums/package_metadata.rb b/app/models/concerns/enums/package_metadata.rb
new file mode 100644
index 00000000000..e15fe758e69
--- /dev/null
+++ b/app/models/concerns/enums/package_metadata.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module Enums
+ class PackageMetadata
+ PURL_TYPES = {
+ composer: 1,
+ conan: 2,
+ gem: 3,
+ golang: 4,
+ maven: 5,
+ npm: 6,
+ nuget: 7,
+ pypi: 8
+ }.with_indifferent_access.freeze
+
+ def self.purl_types
+ PURL_TYPES
+ end
+ end
+end
diff --git a/app/models/concerns/exportable.rb b/app/models/concerns/exportable.rb
new file mode 100644
index 00000000000..066a44912be
--- /dev/null
+++ b/app/models/concerns/exportable.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module Exportable
+ extend ActiveSupport::Concern
+
+ def readable_records(association, current_user: nil)
+ association_records = try(association)
+ return unless association_records.present?
+
+ if has_many_association?(association)
+ DeclarativePolicy.user_scope do
+ association_records.select { |record| readable_record?(record, current_user) }
+ end
+ else
+ readable_record?(association_records, current_user) ? association_records : nil
+ end
+ end
+
+ def exportable_association?(association, current_user: nil)
+ return false unless respond_to?(association)
+ return true if has_many_association?(association)
+
+ readable = try(association)
+ return true if readable.nil?
+
+ readable_record?(readable, current_user)
+ end
+
+ def restricted_associations(keys)
+ exportable_restricted_associations & keys
+ end
+
+ def has_many_association?(association_name)
+ self.class.reflect_on_association(association_name)&.macro == :has_many
+ end
+
+ private
+
+ def exportable_restricted_associations
+ []
+ end
+
+ def readable_record?(record, user)
+ if record.respond_to?(:exportable_record?)
+ record.exportable_record?(user)
+ else
+ record.readable_by?(user)
+ end
+ end
+end
diff --git a/app/models/concerns/group_descendant.rb b/app/models/concerns/group_descendant.rb
index b376537a418..224ac8930b5 100644
--- a/app/models/concerns/group_descendant.rb
+++ b/app/models/concerns/group_descendant.rb
@@ -21,7 +21,7 @@ module GroupDescendant
descendants = Array.wrap(descendants).uniq
return [] if descendants.empty?
- unless descendants.all? { |hierarchy| hierarchy.is_a?(GroupDescendant) }
+ unless descendants.all?(GroupDescendant)
raise ArgumentError, _('element is not a hierarchy')
end
diff --git a/app/models/concerns/id_in_ordered.rb b/app/models/concerns/id_in_ordered.rb
index b89409e6841..39067574520 100644
--- a/app/models/concerns/id_in_ordered.rb
+++ b/app/models/concerns/id_in_ordered.rb
@@ -5,7 +5,7 @@ module IdInOrdered
included do
scope :id_in_ordered, -> (ids) do
- raise ArgumentError, "ids must be an array of integers" unless ids.is_a?(Enumerable) && ids.all? { |id| id.is_a?(Integer) }
+ raise ArgumentError, "ids must be an array of integers" unless ids.is_a?(Enumerable) && ids.all?(Integer)
# No need to sort if no more than 1 and the sorting code doesn't work
# with an empty array
diff --git a/app/models/concerns/integrations/has_web_hook.rb b/app/models/concerns/integrations/has_web_hook.rb
index e622faf4a51..dcf14a4c7dc 100644
--- a/app/models/concerns/integrations/has_web_hook.rb
+++ b/app/models/concerns/integrations/has_web_hook.rb
@@ -42,9 +42,9 @@ module Integrations
end
# Execute the webhook, creating it if necessary.
- def execute_web_hook!(*args)
+ def execute_web_hook!(...)
update_web_hook!
- service_hook.execute(*args)
+ service_hook.execute(...)
end
end
end
diff --git a/app/models/concerns/issuable_link.rb b/app/models/concerns/issuable_link.rb
index c319d685362..7f29083d6c6 100644
--- a/app/models/concerns/issuable_link.rb
+++ b/app/models/concerns/issuable_link.rb
@@ -20,6 +20,12 @@ module IssuableLink
def issuable_type
raise NotImplementedError
end
+
+ # Used to get the available types for the API
+ # overriden in EE
+ def available_link_types
+ [TYPE_RELATES_TO]
+ end
end
included do
diff --git a/app/models/concerns/issue_parent.rb b/app/models/concerns/issue_parent.rb
new file mode 100644
index 00000000000..c1fcbdcfc12
--- /dev/null
+++ b/app/models/concerns/issue_parent.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+# == IssuParent
+#
+# Used as a common ancestor for Group and Project so we can allow a polymorphic
+# Types::GlobalIDType[::IssueParent] in the GraphQL API
+#
+# Used by Project, Group
+#
+module IssueParent
+end
diff --git a/app/models/concerns/noteable.rb b/app/models/concerns/noteable.rb
index eed396f785b..7addcf9e2ec 100644
--- a/app/models/concerns/noteable.rb
+++ b/app/models/concerns/noteable.rb
@@ -106,9 +106,9 @@ module Noteable
relations << discussion_notes.select(
"'notes' AS table_name",
- 'discussion_id',
'MIN(id) AS id',
- 'MIN(created_at) AS created_at'
+ 'MIN(created_at) AS created_at',
+ 'ARRAY_AGG(id) AS ids'
).with_notes_filter(notes_filter)
.group(:discussion_id)
@@ -116,17 +116,19 @@ module Noteable
relations += synthetic_note_ids_relations
end
- Note.from_union(relations, remove_duplicates: false).fresh
+ Note.from_union(relations, remove_duplicates: false)
+ .select(:table_name, :id, :created_at, :ids)
+ .fresh
end
def capped_notes_count(max)
notes.limit(max).count
end
- def grouped_diff_discussions(*args)
+ def grouped_diff_discussions(...)
# Doesn't use `discussion_notes`, because this may include commit diff notes
# besides MR diff notes, that we do not want to display on the MR Changes tab.
- notes.inc_relations_for_view(self).grouped_diff_discussions(*args)
+ notes.inc_relations_for_view(self).grouped_diff_discussions(...)
end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
@@ -223,15 +225,16 @@ module Noteable
# currently multiple models include Noteable concern, but not all of them support
# all resource events, so we check if given model supports given resource event.
if respond_to?(:resource_label_events)
- relations << resource_label_events.select("'resource_label_events'", "'NULL'", :id, :created_at)
+ relations << resource_label_events.select("'resource_label_events'", 'MIN(id)', :created_at, 'ARRAY_AGG(id)')
+ .group(:created_at, :user_id)
end
if respond_to?(:resource_state_events)
- relations << resource_state_events.select("'resource_state_events'", "'NULL'", :id, :created_at)
+ relations << resource_state_events.select("'resource_state_events'", :id, :created_at, 'ARRAY_FILL(id, ARRAY[1])')
end
if respond_to?(:resource_milestone_events)
- relations << resource_milestone_events.select("'resource_milestone_events'", "'NULL'", :id, :created_at)
+ relations << resource_milestone_events.select("'resource_milestone_events'", :id, :created_at, 'ARRAY_FILL(id, ARRAY[1])')
end
relations
diff --git a/app/models/concerns/prometheus_adapter.rb b/app/models/concerns/prometheus_adapter.rb
index df297017119..b85ac9ad4a6 100644
--- a/app/models/concerns/prometheus_adapter.rb
+++ b/app/models/concerns/prometheus_adapter.rb
@@ -49,7 +49,9 @@ module PrometheusAdapter
query_class = query_klass_for(query_name)
query_args = build_query_args(*args)
- with_reactive_cache(query_class.name, *query_args, &query_class.method(:transform_reactive_result))
+ with_reactive_cache(query_class.name, *query_args) do |result|
+ query_class.transform_reactive_result(result)
+ end
end
# Cache metrics for specific environment
diff --git a/app/models/concerns/reactive_caching.rb b/app/models/concerns/reactive_caching.rb
index 9ed2070d11c..aa0fced99c4 100644
--- a/app/models/concerns/reactive_caching.rb
+++ b/app/models/concerns/reactive_caching.rb
@@ -122,8 +122,8 @@ module ReactiveCaching
worker_class.perform_async(self.class, id, *args)
end
- def keep_alive_reactive_cache!(*args)
- Rails.cache.write(alive_reactive_cache_key(*args), true, expires_in: self.class.reactive_cache_lifetime)
+ def keep_alive_reactive_cache!(...)
+ Rails.cache.write(alive_reactive_cache_key(...), true, expires_in: self.class.reactive_cache_lifetime)
end
def full_reactive_cache_key(*qualifiers)
@@ -145,8 +145,8 @@ module ReactiveCaching
Gitlab::ExclusiveLease.cancel(full_reactive_cache_key(*args), uuid)
end
- def within_reactive_cache_lifetime?(*args)
- Rails.cache.exist?(alive_reactive_cache_key(*args))
+ def within_reactive_cache_lifetime?(...)
+ Rails.cache.exist?(alive_reactive_cache_key(...))
end
def enqueuing_update(*args)
diff --git a/app/models/concerns/require_email_verification.rb b/app/models/concerns/require_email_verification.rb
index cf6a31e6ebd..5ff4f520d24 100644
--- a/app/models/concerns/require_email_verification.rb
+++ b/app/models/concerns/require_email_verification.rb
@@ -45,8 +45,9 @@ module RequireEmailVerification
private
def override_devise_lockable?
- strong_memoize(:override_devise_lockable) do
- Feature.enabled?(:require_email_verification, self) && !two_factor_enabled?
- end
+ Feature.enabled?(:require_email_verification, self) &&
+ !two_factor_enabled? &&
+ Feature.disabled?(:skip_require_email_verification, self, type: :ops)
end
+ strong_memoize_attr :override_devise_lockable?
end
diff --git a/app/models/concerns/sensitive_serializable_hash.rb b/app/models/concerns/sensitive_serializable_hash.rb
index 794748483e4..5a9b75d4db8 100644
--- a/app/models/concerns/sensitive_serializable_hash.rb
+++ b/app/models/concerns/sensitive_serializable_hash.rb
@@ -24,12 +24,12 @@ module SensitiveSerializableHash
options[:except].concat self.class.attributes_exempt_from_serializable_hash
- if self.class.respond_to?(:encrypted_attributes)
- options[:except].concat self.class.encrypted_attributes.keys
+ if self.class.respond_to?(:attr_encrypted_attributes)
+ options[:except].concat self.class.attr_encrypted_attributes.keys
# Per https://github.com/attr-encrypted/attr_encrypted/blob/a96693e9a2a25f4f910bf915e29b0f364f277032/lib/attr_encrypted.rb#L413
- options[:except].concat self.class.encrypted_attributes.values.map { |v| v[:attribute] }
- options[:except].concat self.class.encrypted_attributes.values.map { |v| "#{v[:attribute]}_iv" }
+ options[:except].concat self.class.attr_encrypted_attributes.values.map { |v| v[:attribute] }
+ options[:except].concat self.class.attr_encrypted_attributes.values.map { |v| "#{v[:attribute]}_iv" }
end
super(options)
diff --git a/app/models/concerns/sha_attribute.rb b/app/models/concerns/sha_attribute.rb
index 701d2fda5c5..35c48c15fb2 100644
--- a/app/models/concerns/sha_attribute.rb
+++ b/app/models/concerns/sha_attribute.rb
@@ -22,7 +22,7 @@ module ShaAttribute
class_methods do
def sha_attribute(name)
- return if ENV['STATIC_VERIFICATION']
+ return if Gitlab::Environment.static_verification?
sha_attribute_fields << name
@@ -34,7 +34,7 @@ module ShaAttribute
end
def sha256_attribute(name)
- return if ENV['STATIC_VERIFICATION']
+ return if Gitlab::Environment.static_verification?
sha256_attribute_fields << name
diff --git a/app/models/concerns/spammable.rb b/app/models/concerns/spammable.rb
index d27b451892a..fba923e843a 100644
--- a/app/models/concerns/spammable.rb
+++ b/app/models/concerns/spammable.rb
@@ -123,6 +123,6 @@ module Spammable
# Override in Spammable if differs
def allow_possible_spam?
- Feature.enabled?(:allow_possible_spam, project)
+ Gitlab::CurrentSettings.allow_possible_spam
end
end
diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb
index 05addcf83d2..f9eba4cc2fe 100644
--- a/app/models/concerns/taskable.rb
+++ b/app/models/concerns/taskable.rb
@@ -24,10 +24,28 @@ module Taskable
(\s.+) # followed by whitespace and some text.
}x.freeze
+ # ignore tasks in code or html comment blocks. HTML blocks
+ # are ok as we allow tasks inside <detail> blocks
+ REGEX = %r{
+ #{::Gitlab::Regex.markdown_code_or_html_comments}
+ |
+ (?<task_item>
+ #{ITEM_PATTERN}
+ )
+ }mx.freeze
+
def self.get_tasks(content)
- content.to_s.scan(ITEM_PATTERN).map do |prefix, checkbox, label|
- TaskList::Item.new("#{prefix} #{checkbox}", label.strip)
+ items = []
+
+ content.to_s.scan(REGEX) do
+ next unless $~[:task_item]
+
+ $~[:task_item].scan(ITEM_PATTERN) do |prefix, checkbox, label|
+ items << TaskList::Item.new("#{prefix.strip} #{checkbox}", label.strip)
+ end
end
+
+ items
end
def self.get_updated_tasks(old_content:, new_content:)
@@ -67,10 +85,10 @@ module Taskable
checklist_item_noun = n_('checklist item', 'checklist items', sum.item_count)
if short
format(s_('Tasks|%{complete_count}/%{total_count} %{checklist_item_noun}'),
-checklist_item_noun: checklist_item_noun, complete_count: sum.complete_count, total_count: sum.item_count)
+ checklist_item_noun: checklist_item_noun, complete_count: sum.complete_count, total_count: sum.item_count)
else
format(s_('Tasks|%{complete_count} of %{total_count} %{checklist_item_noun} completed'),
-checklist_item_noun: checklist_item_noun, complete_count: sum.complete_count, total_count: sum.item_count)
+ checklist_item_noun: checklist_item_noun, complete_count: sum.complete_count, total_count: sum.item_count)
end
end
diff --git a/app/models/concerns/token_authenticatable.rb b/app/models/concerns/token_authenticatable.rb
index d91ec161b84..cc3e8f174b3 100644
--- a/app/models/concerns/token_authenticatable.rb
+++ b/app/models/concerns/token_authenticatable.rb
@@ -86,7 +86,7 @@ module TokenAuthenticatable
def token_authenticatable_module
@token_authenticatable_module ||=
- const_set(:TokenAuthenticatable, Module.new).tap(&method(:include))
+ const_set(:TokenAuthenticatable, Module.new).tap { |mod| include mod }
end
end
end
diff --git a/app/models/concerns/vulnerability_finding_helpers.rb b/app/models/concerns/vulnerability_finding_helpers.rb
index b5d48260072..1e8a290c050 100644
--- a/app/models/concerns/vulnerability_finding_helpers.rb
+++ b/app/models/concerns/vulnerability_finding_helpers.rb
@@ -49,7 +49,7 @@ module VulnerabilityFindingHelpers
finding_data = report_finding.to_hash.except(:compare_key, :identifiers, :location, :scanner, :links, :signatures,
:flags, :evidence)
- identifiers = report_finding.identifiers.map do |identifier|
+ identifiers = report_finding.identifiers.uniq(&:fingerprint).map do |identifier|
Vulnerabilities::Identifier.new(identifier.to_hash.merge({ project: project }))
end
signatures = report_finding.signatures.map do |signature|
diff --git a/app/models/concerns/web_hooks/auto_disabling.rb b/app/models/concerns/web_hooks/auto_disabling.rb
new file mode 100644
index 00000000000..2cc17a6f185
--- /dev/null
+++ b/app/models/concerns/web_hooks/auto_disabling.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+module WebHooks
+ module AutoDisabling
+ extend ActiveSupport::Concern
+
+ included do
+ # A hook is disabled if:
+ #
+ # - we are no longer in the grace-perod (recent_failures > ?)
+ # - and either:
+ # - disabled_until is nil (i.e. this was set by WebHook#fail!)
+ # - or disabled_until is in the future (i.e. this was set by WebHook#backoff!)
+ scope :disabled, -> do
+ where('recent_failures > ? AND (disabled_until IS NULL OR disabled_until >= ?)',
+ WebHook::FAILURE_THRESHOLD, Time.current)
+ end
+
+ # A hook is executable if:
+ #
+ # - we are still in the grace-period (recent_failures <= ?)
+ # - OR we have exceeded the grace period and neither of the following is true:
+ # - disabled_until is nil (i.e. this was set by WebHook#fail!)
+ # - disabled_until is in the future (i.e. this was set by WebHook#backoff!)
+ scope :executable, -> do
+ where('recent_failures <= ? OR (recent_failures > ? AND (disabled_until IS NOT NULL) AND (disabled_until < ?))',
+ WebHook::FAILURE_THRESHOLD, WebHook::FAILURE_THRESHOLD, Time.current)
+ end
+ end
+
+ def executable?
+ !temporarily_disabled? && !permanently_disabled?
+ end
+
+ def temporarily_disabled?
+ return false if recent_failures <= WebHook::FAILURE_THRESHOLD
+
+ disabled_until.present? && disabled_until >= Time.current
+ end
+
+ def permanently_disabled?
+ return false if disabled_until.present?
+
+ recent_failures > WebHook::FAILURE_THRESHOLD
+ end
+
+ def disable!
+ return if permanently_disabled?
+
+ super
+ end
+
+ def backoff!
+ return if permanently_disabled? || (backoff_count >= WebHook::MAX_FAILURES && temporarily_disabled?)
+
+ super
+ end
+
+ def alert_status
+ if temporarily_disabled?
+ :temporarily_disabled
+ elsif permanently_disabled?
+ :disabled
+ else
+ :executable
+ end
+ end
+ end
+end
diff --git a/app/models/concerns/web_hooks/has_web_hooks.rb b/app/models/concerns/web_hooks/has_web_hooks.rb
new file mode 100644
index 00000000000..161ce106b9b
--- /dev/null
+++ b/app/models/concerns/web_hooks/has_web_hooks.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module WebHooks
+ module HasWebHooks
+ extend ActiveSupport::Concern
+
+ WEB_HOOK_CACHE_EXPIRY = 1.hour
+
+ def any_hook_failed?
+ hooks.disabled.exists?
+ end
+
+ def web_hook_failure_redis_key
+ "any_web_hook_failed:#{id}"
+ end
+
+ def last_failure_redis_key
+ "web_hooks:last_failure:project-#{id}"
+ end
+
+ def get_web_hook_failure
+ Gitlab::Redis::SharedState.with do |redis|
+ current = redis.get(web_hook_failure_redis_key)
+
+ Gitlab::Utils.to_boolean(current) if current
+ end
+ end
+
+ def fetch_web_hook_failure
+ Gitlab::Redis::SharedState.with do |_redis|
+ current = get_web_hook_failure
+ next current unless current.nil?
+
+ cache_web_hook_failure
+ end
+ end
+
+ def cache_web_hook_failure(state = any_hook_failed?)
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.set(web_hook_failure_redis_key, state.to_s, ex: WEB_HOOK_CACHE_EXPIRY)
+
+ state
+ end
+ end
+ end
+end
diff --git a/app/models/concerns/web_hooks/unstoppable.rb b/app/models/concerns/web_hooks/unstoppable.rb
new file mode 100644
index 00000000000..26284fe3c36
--- /dev/null
+++ b/app/models/concerns/web_hooks/unstoppable.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module WebHooks
+ module Unstoppable
+ extend ActiveSupport::Concern
+
+ included do
+ scope :executable, -> { all }
+
+ scope :disabled, -> { none }
+ end
+
+ def executable?
+ true
+ end
+
+ def temporarily_disabled?
+ false
+ end
+
+ def permanently_disabled?
+ false
+ end
+
+ def alert_status
+ :executable
+ end
+ end
+end
diff --git a/app/models/concerns/work_item_resource_event.rb b/app/models/concerns/work_item_resource_event.rb
index d0323feb029..ddf39787f63 100644
--- a/app/models/concerns/work_item_resource_event.rb
+++ b/app/models/concerns/work_item_resource_event.rb
@@ -5,6 +5,18 @@ module WorkItemResourceEvent
included do
belongs_to :work_item, foreign_key: 'issue_id'
+
+ scope :with_work_item, -> { preload(:work_item) }
+
+ # These events are created also on non work items, e.g. MRs, Epic however system notes subscription
+ # is only implemented on work items, so we do check if this event is linked to an work item. This can be
+ # expanded to other issuables later on.
+ after_commit :trigger_note_subscription_create, on: :create, if: -> { work_item.present? }
+ end
+
+ # System notes are not updated or deleted, so firing just the noteCreated event.
+ def trigger_note_subscription_create(events: self)
+ GraphqlTriggers.work_item_note_created(work_item.to_gid, events)
end
def work_item_synthetic_system_note(events: nil)
diff --git a/app/models/concerns/x509_serial_number_attribute.rb b/app/models/concerns/x509_serial_number_attribute.rb
index 9dc53859ac0..b65736b7924 100644
--- a/app/models/concerns/x509_serial_number_attribute.rb
+++ b/app/models/concerns/x509_serial_number_attribute.rb
@@ -5,7 +5,7 @@ module X509SerialNumberAttribute
class_methods do
def x509_serial_number_attribute(name)
- return if ENV['STATIC_VERIFICATION']
+ return if Gitlab::Environment.static_verification?
validate_binary_column_exists!(name) unless Rails.env.production?