diff options
Diffstat (limited to 'app/models/concerns')
29 files changed, 343 insertions, 79 deletions
diff --git a/app/models/concerns/analytics/cycle_analytics/stage.rb b/app/models/concerns/analytics/cycle_analytics/stage.rb index 2a0274f5706..7bb6004ca83 100644 --- a/app/models/concerns/analytics/cycle_analytics/stage.rb +++ b/app/models/concerns/analytics/cycle_analytics/stage.rb @@ -10,6 +10,7 @@ module Analytics included do belongs_to :start_event_label, class_name: 'GroupLabel', optional: true belongs_to :end_event_label, class_name: 'GroupLabel', optional: true + belongs_to :stage_event_hash, class_name: 'Analytics::CycleAnalytics::StageEventHash', foreign_key: :stage_event_hash_id, optional: true validates :name, presence: true validates :name, exclusion: { in: Gitlab::Analytics::CycleAnalytics::DefaultStages.names }, if: :custom? @@ -28,6 +29,9 @@ module Analytics scope :ordered, -> { order(:relative_position, :id) } scope :for_list, -> { includes(:start_event_label, :end_event_label).ordered } scope :by_value_stream, -> (value_stream) { where(value_stream_id: value_stream.id) } + + before_save :ensure_stage_event_hash_id + after_commit :cleanup_old_stage_event_hash end def parent=(_) @@ -133,6 +137,20 @@ module Analytics .id_in(label_id) .exists? end + + def ensure_stage_event_hash_id + previous_stage_event_hash = stage_event_hash&.hash_sha256 + + if previous_stage_event_hash.blank? || events_hash_code != previous_stage_event_hash + self.stage_event_hash_id = Analytics::CycleAnalytics::StageEventHash.record_id_by_hash_sha256(events_hash_code) + end + end + + def cleanup_old_stage_event_hash + if stage_event_hash_id_previously_changed? && stage_event_hash_id_previously_was + Analytics::CycleAnalytics::StageEventHash.cleanup_if_unused(stage_event_hash_id_previously_was) + end + end end end end diff --git a/app/models/concerns/any_field_validation.rb b/app/models/concerns/any_field_validation.rb deleted file mode 100644 index 987c4e7800e..00000000000 --- a/app/models/concerns/any_field_validation.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -# This module enables a record to be valid if any field is present -# -# Overwrite one_of_required_fields to set one of which fields must be present -module AnyFieldValidation - extend ActiveSupport::Concern - - included do - validate :any_field_present - end - - private - - def any_field_present - return unless one_of_required_fields.all? { |field| self[field].blank? } - - errors.add(:base, _("At least one field of %{one_of_required_fields} must be present") % - { one_of_required_fields: one_of_required_fields }) - end - - def one_of_required_fields - raise NotImplementedError - end -end diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb index 79b622c8dad..44d9beff27e 100644 --- a/app/models/concerns/cache_markdown_field.rb +++ b/app/models/concerns/cache_markdown_field.rb @@ -160,6 +160,8 @@ module CacheMarkdownField # We can only store mentions if the mentionable is a database object return unless self.is_a?(ApplicationRecord) + return store_mentions_without_subtransaction! if Feature.enabled?(:store_mentions_without_subtransaction, default_enabled: :yaml) + refs = all_references(self.author) references = {} @@ -190,6 +192,29 @@ module CacheMarkdownField true end + def store_mentions_without_subtransaction! + identifier = user_mention_identifier + + # this may happen due to notes polymorphism, so noteable_id may point to a record + # that no longer exists as we cannot have FK on noteable_id + return if identifier.blank? + + refs = all_references(self.author) + + references = {} + references[:mentioned_users_ids] = refs.mentioned_user_ids.presence + references[:mentioned_groups_ids] = refs.mentioned_group_ids.presence + references[:mentioned_projects_ids] = refs.mentioned_project_ids.presence + + if references.compact.any? + user_mention_class.upsert(references.merge(identifier), unique_by: identifier.compact.keys) + else + user_mention_class.delete_by(identifier) + end + + true + end + def mentionable_attributes_changed?(changes = saved_changes) return false unless is_a?(Mentionable) diff --git a/app/models/concerns/cascading_namespace_setting_attribute.rb b/app/models/concerns/cascading_namespace_setting_attribute.rb index 5d24e15d518..e58e5ddc966 100644 --- a/app/models/concerns/cascading_namespace_setting_attribute.rb +++ b/app/models/concerns/cascading_namespace_setting_attribute.rb @@ -127,7 +127,7 @@ module CascadingNamespaceSettingAttribute end def alias_boolean(attribute) - return unless Gitlab::Database.exists? && type_for_attribute(attribute).type == :boolean + return unless Gitlab::Database.main.exists? && type_for_attribute(attribute).type == :boolean alias_method :"#{attribute}?", attribute end diff --git a/app/models/concerns/ci/has_status.rb b/app/models/concerns/ci/has_status.rb index f3c254053b5..c1299e3d468 100644 --- a/app/models/concerns/ci/has_status.rb +++ b/app/models/concerns/ci/has_status.rb @@ -93,6 +93,7 @@ module Ci scope :running_or_pending, -> { with_status(:running, :pending) } scope :finished, -> { with_status(:success, :failed, :canceled) } scope :failed_or_canceled, -> { with_status(:failed, :canceled) } + scope :complete, -> { with_status(completed_statuses) } scope :incomplete, -> { without_statuses(completed_statuses) } scope :cancelable, -> do diff --git a/app/models/concerns/ci/metadatable.rb b/app/models/concerns/ci/metadatable.rb index 114435d5a21..ec86746ae54 100644 --- a/app/models/concerns/ci/metadatable.rb +++ b/app/models/concerns/ci/metadatable.rb @@ -76,14 +76,8 @@ module Ci end def write_metadata_attribute(legacy_key, metadata_key, value) - # save to metadata or this model depending on the state of feature flag - if Feature.enabled?(:ci_build_metadata_config, project, default_enabled: :yaml) - ensure_metadata.write_attribute(metadata_key, value) - write_attribute(legacy_key, nil) - else - write_attribute(legacy_key, value) - metadata&.write_attribute(metadata_key, nil) - end + ensure_metadata.write_attribute(metadata_key, value) + write_attribute(legacy_key, nil) end end end diff --git a/app/models/concerns/ci/namespaced_model_name.rb b/app/models/concerns/ci/namespaced_model_name.rb new file mode 100644 index 00000000000..e941a3a7a0c --- /dev/null +++ b/app/models/concerns/ci/namespaced_model_name.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Ci + module NamespacedModelName + extend ActiveSupport::Concern + + class_methods do + def model_name + @model_name ||= ActiveModel::Name.new(self, Ci) + end + end + end +end diff --git a/app/models/concerns/counter_attribute.rb b/app/models/concerns/counter_attribute.rb index 829b2a6ef21..4bfeba338d2 100644 --- a/app/models/concerns/counter_attribute.rb +++ b/app/models/concerns/counter_attribute.rb @@ -128,8 +128,7 @@ module CounterAttribute end def counter_attribute_enabled?(attribute) - Feature.enabled?(:efficient_counter_attribute, project) && - self.class.counter_attributes.include?(attribute) + self.class.counter_attributes.include?(attribute) end private diff --git a/app/models/concerns/each_batch.rb b/app/models/concerns/each_batch.rb index a59f00d73ec..443e1ab53b4 100644 --- a/app/models/concerns/each_batch.rb +++ b/app/models/concerns/each_batch.rb @@ -91,7 +91,11 @@ module EachBatch # Any ORDER BYs are useless for this relation and can lead to less # efficient UPDATE queries, hence we get rid of it. - yield relation.except(:order), index + relation = relation.except(:order) + + # Using unscoped is necessary to prevent leaking the current scope used by + # ActiveRecord to chain `each_batch` method. + unscoped { yield relation, index } break unless stop end diff --git a/app/models/concerns/enums/ci/pipeline.rb b/app/models/concerns/enums/ci/pipeline.rb index c42b046592f..94d11c871ca 100644 --- a/app/models/concerns/enums/ci/pipeline.rb +++ b/app/models/concerns/enums/ci/pipeline.rb @@ -37,7 +37,9 @@ module Enums merge_request_event: 10, external_pull_request_event: 11, parent_pipeline: 12, - ondemand_dast_scan: 13 + ondemand_dast_scan: 13, + ondemand_dast_validation: 14, + security_orchestration_policy: 15 } end @@ -48,8 +50,10 @@ module Enums # parent pipeline. It's up to the parent to affect the ref CI status # - when an ondemand_dast_scan pipeline runs it is for testing purpose and should # not affect the ref CI status. + # - when an ondemand_dast_validation pipeline runs it is for validating a DAST site + # profile and should not affect the ref CI status. def self.dangling_sources - sources.slice(:webide, :parent_pipeline, :ondemand_dast_scan) + sources.slice(:webide, :parent_pipeline, :ondemand_dast_scan, :ondemand_dast_validation, :security_orchestration_policy) end # CI sources are those pipeline events that affect the CI status of the ref diff --git a/app/models/concerns/expirable.rb b/app/models/concerns/expirable.rb index 512822089ba..e029ada84f0 100644 --- a/app/models/concerns/expirable.rb +++ b/app/models/concerns/expirable.rb @@ -13,6 +13,9 @@ module Expirable expires? && expires_at <= Time.current end + # Used in subclasses that override expired? + alias_method :expired_original?, :expired? + def expires? expires_at.present? end diff --git a/app/models/concerns/has_integrations.rb b/app/models/concerns/has_integrations.rb index 25650ae56ad..76e03d68600 100644 --- a/app/models/concerns/has_integrations.rb +++ b/app/models/concerns/has_integrations.rb @@ -4,18 +4,6 @@ module HasIntegrations extend ActiveSupport::Concern class_methods do - def with_custom_integration_for(integration, page = nil, per = nil) - custom_integration_project_ids = Integration - .select(:project_id) - .where(type: integration.type) - .where(inherit_from_id: nil) - .where.not(project_id: nil) - .page(page) - .per(per) - - Project.where(id: custom_integration_project_ids) - end - def without_integration(integration) integrations = Integration .select('1') diff --git a/app/models/concerns/incident_management/escalatable.rb b/app/models/concerns/incident_management/escalatable.rb new file mode 100644 index 00000000000..78dce63f59e --- /dev/null +++ b/app/models/concerns/incident_management/escalatable.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true + +module IncidentManagement + # Shared functionality for a `#status` field, representing + # whether action is required. In EE, this corresponds + # to paging functionality with EscalationPolicies. + # + # This module is only responsible for setting the status and + # possible status-related timestamps (EX triggered_at/resolved_at) + # for the implementing class. The relationships between these + # values and other related timestamps/logic should be managed from + # the object class itself. (EX Alert#ended_at = Alert#resolved_at) + module Escalatable + extend ActiveSupport::Concern + + STATUSES = { + triggered: 0, + acknowledged: 1, + resolved: 2, + ignored: 3 + }.freeze + + STATUS_DESCRIPTIONS = { + triggered: 'Investigation has not started', + acknowledged: 'Someone is actively investigating the problem', + resolved: 'The problem has been addressed', + ignored: 'No action will be taken' + }.freeze + + included do + validates :status, presence: true + + # Ascending sort order sorts statuses: Ignored > Resolved > Acknowledged > Triggered + # Descending sort order sorts statuses: Triggered > Acknowledged > Resolved > Ignored + # https://gitlab.com/gitlab-org/gitlab/-/issues/221242#what-is-the-expected-correct-behavior + scope :order_status, -> (sort_order) { order(status: sort_order == :asc ? :desc : :asc) } + + state_machine :status, initial: :triggered do + state :triggered, value: STATUSES[:triggered] + + state :acknowledged, value: STATUSES[:acknowledged] + + state :resolved, value: STATUSES[:resolved] do + validates :resolved_at, presence: true + end + + state :ignored, value: STATUSES[:ignored] + + state :triggered, :acknowledged, :ignored do + validates :resolved_at, absence: true + end + + event :trigger do + transition any => :triggered + end + + event :acknowledge do + transition any => :acknowledged + end + + event :resolve do + transition any => :resolved + end + + event :ignore do + transition any => :ignored + end + + before_transition to: [:triggered, :acknowledged, :ignored] do |escalatable, _transition| + escalatable.resolved_at = nil + end + + before_transition to: :resolved do |escalatable, transition| + resolved_at = transition.args.first + escalatable.resolved_at = resolved_at || Time.current + end + end + + class << self + def status_value(name) + state_machine_statuses[name] + end + + def status_name(raw_status) + state_machine_statuses.key(raw_status) + end + + def status_names + @status_names ||= state_machine_statuses.keys + end + + private + + def state_machine_statuses + @state_machine_statuses ||= state_machines[:status].states.to_h { |s| [s.name, s.value] } + end + end + + def status_event_for(status) + self.class.state_machines[:status].events.transitions_for(self, to: status.to_s.to_sym).first&.event + end + end + end +end diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index d5e2e63402f..8d0f8b01d64 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -152,7 +152,7 @@ module Issuable participant :notes_with_associations participant :assignees - strip_attributes :title + strip_attributes! :title class << self def labels_hash @@ -374,6 +374,8 @@ module Issuable grouping_columns << milestone_table[:due_date] elsif %w(merged_at_desc merged_at_asc).include?(sort) grouping_columns << MergeRequest::Metrics.arel_table[:merged_at] + elsif %w(closed_at_desc closed_at_asc).include?(sort) + grouping_columns << MergeRequest::Metrics.arel_table[:closed_at] end grouping_columns diff --git a/app/models/concerns/limitable.rb b/app/models/concerns/limitable.rb index 41efea65c5a..fab1aa21634 100644 --- a/app/models/concerns/limitable.rb +++ b/app/models/concerns/limitable.rb @@ -9,6 +9,7 @@ module Limitable class_attribute :limit_relation class_attribute :limit_name class_attribute :limit_feature_flag + class_attribute :limit_feature_flag_for_override # Allows selectively disabling by actor (as per https://docs.gitlab.com/ee/development/feature_flags/#selectively-disable-by-actor) self.limit_name = self.name.demodulize.tableize validate :validate_plan_limit_not_exceeded, on: :create @@ -28,6 +29,7 @@ module Limitable scope_relation = self.public_send(limit_scope) # rubocop:disable GitlabSecurity/PublicSend return unless scope_relation return if limit_feature_flag && ::Feature.disabled?(limit_feature_flag, scope_relation, default_enabled: :yaml) + return if limit_feature_flag_for_override && ::Feature.enabled?(limit_feature_flag_for_override, scope_relation, default_enabled: :yaml) relation = limit_relation ? self.public_send(limit_relation) : self.class.where(limit_scope => scope_relation) # rubocop:disable GitlabSecurity/PublicSend limits = scope_relation.actual_limits diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb index f1baa923ec5..4df9e32d8ec 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -161,6 +161,21 @@ module Mentionable create_cross_references!(author) end + def user_mention_class + user_mention_association.klass + end + + # Identifier for the user mention that is parsed from model description rather then its related notes. + # Models that have a description attribute like Issue, MergeRequest, Epic, Snippet may have such a user mention. + # Other mentionable models like DesignManagement::Design, will never have such record as those do not have + # a description attribute. + def user_mention_identifier + { + user_mention_association.foreign_key => id, + note_id: nil + } + end + private def extracted_mentionables(refs) @@ -199,6 +214,10 @@ module Mentionable {} end + def user_mention_association + association(:user_mentions).reflection + end + # User mention that is parsed from model description rather then its related notes. # Models that have a description attribute like Issue, MergeRequest, Epic, Snippet may have such a user mention. # Other mentionable models like Commit, DesignManagement::Design, will never have such record as those do not have diff --git a/app/models/concerns/packages/debian/distribution.rb b/app/models/concerns/packages/debian/distribution.rb index 159f0044c82..196bec04be6 100644 --- a/app/models/concerns/packages/debian/distribution.rb +++ b/app/models/concerns/packages/debian/distribution.rb @@ -77,23 +77,16 @@ module Packages validates container_type, presence: true validates :file_store, presence: true - - validates :file_signature, absence: true - validates :signing_keys, absence: true + validates :signed_file_store, presence: true scope :with_container, ->(subject) { where(container_type => subject) } scope :with_codename, ->(codename) { where(codename: codename) } scope :with_suite, ->(suite) { where(suite: suite) } scope :with_codename_or_suite, ->(codename_or_suite) { with_codename(codename_or_suite).or(with_suite(codename_or_suite)) } - attr_encrypted :signing_keys, - mode: :per_attribute_iv, - key: Settings.attr_encrypted_db_key_base_32, - algorithm: 'aes-256-gcm', - encode: false, - encode_iv: false - mount_file_store_uploader Packages::Debian::DistributionReleaseFileUploader + mount_uploader :signed_file, Packages::Debian::DistributionReleaseFileUploader + after_save :update_signed_file_store, if: :saved_change_to_signed_file? def component_names components.pluck(:name).sort @@ -131,6 +124,12 @@ module Packages self.class.with_container(container).with_codename(suite).exists? end + + def update_signed_file_store + # The signed_file.object_store is set during `uploader.store!` + # which happens after object is inserted/updated + self.update_column(:signed_file_store, signed_file.object_store) + end end end end diff --git a/app/models/concerns/project_features_compatibility.rb b/app/models/concerns/project_features_compatibility.rb index 484c91e0833..0cab874a240 100644 --- a/app/models/concerns/project_features_compatibility.rb +++ b/app/models/concerns/project_features_compatibility.rb @@ -90,6 +90,13 @@ module ProjectFeaturesCompatibility write_feature_attribute_string(:container_registry_access_level, value) end + # TODO: Remove this method after we drop support for project create/edit APIs to set the + # container_registry_enabled attribute. They can instead set the container_registry_access_level + # attribute. + def container_registry_enabled=(value) + write_feature_attribute_boolean(:container_registry_access_level, value) + end + private def write_feature_attribute_boolean(field, value) diff --git a/app/models/concerns/restricted_signup.rb b/app/models/concerns/restricted_signup.rb new file mode 100644 index 00000000000..587f8c35ff7 --- /dev/null +++ b/app/models/concerns/restricted_signup.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true +module RestrictedSignup + extend ActiveSupport::Concern + + private + + def validate_admin_signup_restrictions(email) + return if allowed_domain?(email) + + if allowlist_present? + return _('domain is not authorized for sign-up.') + elsif denied_domain?(email) + return _('is not from an allowed domain.') + elsif restricted_email?(email) + return _('is not allowed. Try again with a different email address, or contact your GitLab admin.') + end + + nil + end + + def denied_domain?(email) + return false unless Gitlab::CurrentSettings.domain_denylist_enabled? + + denied_domains = Gitlab::CurrentSettings.domain_denylist + denied_domains.present? && domain_matches?(denied_domains, email) + end + + def allowlist_present? + Gitlab::CurrentSettings.domain_allowlist.present? + end + + def allowed_domain?(email) + allowed_domains = Gitlab::CurrentSettings.domain_allowlist + allowlist_present? && domain_matches?(allowed_domains, email) + end + + def restricted_email?(email) + return false unless Gitlab::CurrentSettings.email_restrictions_enabled? + + restrictions = Gitlab::CurrentSettings.email_restrictions + restrictions.present? && Gitlab::UntrustedRegexp.new(restrictions).match?(email) + end + + def domain_matches?(email_domains, email) + signup_domain = Mail::Address.new(email).domain + email_domains.any? do |domain| + escaped = Regexp.escape(domain).gsub('\*', '.*?') + regexp = Regexp.new "^#{escaped}$", Regexp::IGNORECASE + signup_domain =~ regexp + end + end +end diff --git a/app/models/concerns/select_for_project_authorization.rb b/app/models/concerns/select_for_project_authorization.rb index 4fae36f7b8d..49342e30db6 100644 --- a/app/models/concerns/select_for_project_authorization.rb +++ b/app/models/concerns/select_for_project_authorization.rb @@ -5,7 +5,7 @@ module SelectForProjectAuthorization class_methods do def select_for_project_authorization - select("projects.id AS project_id, members.access_level") + select("projects.id AS project_id", "members.access_level") end def select_as_maintainer_for_project_authorization diff --git a/app/models/concerns/sha256_attribute.rb b/app/models/concerns/sha256_attribute.rb index 4921f7f1a7e..17fda6c806c 100644 --- a/app/models/concerns/sha256_attribute.rb +++ b/app/models/concerns/sha256_attribute.rb @@ -39,7 +39,7 @@ module Sha256Attribute end def database_exists? - Gitlab::Database.exists? + Gitlab::Database.main.exists? end end end diff --git a/app/models/concerns/sha_attribute.rb b/app/models/concerns/sha_attribute.rb index f6f5dbce4b6..27277bc5296 100644 --- a/app/models/concerns/sha_attribute.rb +++ b/app/models/concerns/sha_attribute.rb @@ -32,7 +32,7 @@ module ShaAttribute end def database_exists? - Gitlab::Database.exists? + Gitlab::Database.main.exists? end end end diff --git a/app/models/concerns/spammable.rb b/app/models/concerns/spammable.rb index 2daea388939..4901cd832ff 100644 --- a/app/models/concerns/spammable.rb +++ b/app/models/concerns/spammable.rb @@ -111,7 +111,7 @@ module Spammable end # Override in Spammable if further checks are necessary - def check_for_spam? + def check_for_spam?(user:) true end diff --git a/app/models/concerns/strip_attribute.rb b/app/models/concerns/strip_attribute.rb index 8f6a6244dd3..1c433a3275e 100644 --- a/app/models/concerns/strip_attribute.rb +++ b/app/models/concerns/strip_attribute.rb @@ -7,7 +7,7 @@ # Usage: # # class Milestone < ApplicationRecord -# strip_attributes :title +# strip_attributes! :title # end # # @@ -15,7 +15,7 @@ module StripAttribute extend ActiveSupport::Concern class_methods do - def strip_attributes(*attrs) + def strip_attributes!(*attrs) strip_attrs.concat(attrs) end @@ -25,10 +25,10 @@ module StripAttribute end included do - before_validation :strip_attributes + before_validation :strip_attributes! end - def strip_attributes + def strip_attributes! self.class.strip_attrs.each do |attr| self[attr].strip! if self[attr] && self[attr].respond_to?(:strip!) end diff --git a/app/models/concerns/time_trackable.rb b/app/models/concerns/time_trackable.rb index 89b42eec727..54fe9eac2bc 100644 --- a/app/models/concerns/time_trackable.rb +++ b/app/models/concerns/time_trackable.rb @@ -11,7 +11,7 @@ module TimeTrackable extend ActiveSupport::Concern included do - attr_reader :time_spent, :time_spent_user, :spent_at + attr_reader :time_spent, :time_spent_user, :spent_at, :summary alias_method :time_spent?, :time_spent @@ -20,7 +20,7 @@ module TimeTrackable validates :time_estimate, numericality: { message: 'has an invalid format' }, allow_nil: false validate :check_negative_time_spent - has_many :timelogs, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_many :timelogs, dependent: :destroy, autosave: true # rubocop:disable Cop/ActiveRecordDependent end # rubocop:disable Gitlab/ModuleWithInstanceVariables @@ -29,6 +29,7 @@ module TimeTrackable @time_spent_note_id = options[:note_id] @time_spent_user = User.find(options[:user_id]) @spent_at = options[:spent_at] + @summary = options[:summary] @original_total_time_spent = nil return if @time_spent == 0 @@ -78,7 +79,8 @@ module TimeTrackable time_spent: time_spent, note_id: @time_spent_note_id, user: @time_spent_user, - spent_at: @spent_at + spent_at: @spent_at, + summary: @summary ) end # rubocop:enable Gitlab/ModuleWithInstanceVariables diff --git a/app/models/concerns/timebox.rb b/app/models/concerns/timebox.rb index 8dc58f8dca1..79cbe225e5a 100644 --- a/app/models/concerns/timebox.rb +++ b/app/models/concerns/timebox.rb @@ -106,7 +106,7 @@ module Timebox .where('due_date is NULL or due_date >= ?', start_date) end - strip_attributes :title + strip_attributes! :title alias_attribute :name, :title end diff --git a/app/models/concerns/vulnerability_finding_helpers.rb b/app/models/concerns/vulnerability_finding_helpers.rb index f0e5e010e70..a656856487d 100644 --- a/app/models/concerns/vulnerability_finding_helpers.rb +++ b/app/models/concerns/vulnerability_finding_helpers.rb @@ -2,6 +2,35 @@ module VulnerabilityFindingHelpers extend ActiveSupport::Concern -end + def matches_signatures(other_signatures, other_uuid) + other_signature_types = other_signatures.index_by(&:algorithm_type) + + # highest first + match_result = nil + signatures.sort_by(&:priority).reverse_each do |signature| + matching_other_signature = other_signature_types[signature.algorithm_type] + next if matching_other_signature.nil? + + match_result = matching_other_signature == signature + break + end -VulnerabilityFindingHelpers.prepend_mod_with('VulnerabilityFindingHelpers') + if match_result.nil? + [uuid, *signature_uuids].include?(other_uuid) + else + match_result + end + end + + def signature_uuids + signatures.map do |signature| + hex_sha = signature.signature_hex + ::Security::VulnerabilityUUID.generate( + report_type: report_type, + location_fingerprint: hex_sha, + primary_identifier_fingerprint: primary_identifier&.fingerprint, + project_id: project_id + ) + end + end +end diff --git a/app/models/concerns/vulnerability_finding_signature_helpers.rb b/app/models/concerns/vulnerability_finding_signature_helpers.rb index f98c1e93aaf..71a12b4077b 100644 --- a/app/models/concerns/vulnerability_finding_signature_helpers.rb +++ b/app/models/concerns/vulnerability_finding_signature_helpers.rb @@ -2,6 +2,30 @@ module VulnerabilityFindingSignatureHelpers extend ActiveSupport::Concern -end + # If the location object describes a physical location within a file + # (filename + line numbers), the 'location' algorithm_type should be used + # If the location object describes arbitrary data, then the 'hash' + # algorithm_type should be used. + + ALGORITHM_TYPES = { hash: 1, location: 2, scope_offset: 3 }.with_indifferent_access.freeze + + class_methods do + def priority(algorithm_type) + raise ArgumentError, "No priority for #{algorithm_type.inspect}" unless ALGORITHM_TYPES.key?(algorithm_type) + + ALGORITHM_TYPES[algorithm_type] + end -VulnerabilityFindingSignatureHelpers.prepend_mod_with('VulnerabilityFindingSignatureHelpers') + def algorithm_types + ALGORITHM_TYPES + end + end + + def priority + self.class.priority(algorithm_type) + end + + def algorithm_types + self.class.algorithm_types + end +end diff --git a/app/models/concerns/x509_serial_number_attribute.rb b/app/models/concerns/x509_serial_number_attribute.rb index dbba80eff53..dfb1e151b41 100644 --- a/app/models/concerns/x509_serial_number_attribute.rb +++ b/app/models/concerns/x509_serial_number_attribute.rb @@ -39,7 +39,7 @@ module X509SerialNumberAttribute end def database_exists? - Gitlab::Database.exists? + Gitlab::Database.main.exists? end end end |