Class: Gitlab::BackgroundMigration::FixVulnerabilitiesTransitionedFromDismissedToResolved

Inherits:
BatchedMigrationJob
  • Object
show all
Defined in:
lib/gitlab/background_migration/fix_vulnerabilities_transitioned_from_dismissed_to_resolved.rb

Defined Under Namespace

Classes: Note, Project, StateTransition, User, Vulnerability

Constant Summary collapse

FIRST_APPEARANCE_DATE =

The earliest that records could have appeared on .com was when the feature flag was enabled on 2024-12-05: gitlab.com/gitlab-org/gitlab/-/issues/505711

In self-managed, it could have appeared beginning in the 17.7 release on 2024-12-19.

Date.new(2024, 12, 5)
COMMENT =
"Status changed to dismissed. Reverts a bug that incorrectly set this vulnerability to resolved." \
"For details, see [issue 523433](https://gitlab.com/gitlab-org/gitlab/-/issues/523433)"

Constants inherited from BatchedMigrationJob

BatchedMigrationJob::DEFAULT_FEATURE_CATEGORY, BatchedMigrationJob::MINIMUM_PAUSE_MS

Constants included from Database::DynamicModelHelpers

Database::DynamicModelHelpers::BATCH_SIZE

Instance Method Summary collapse

Methods inherited from BatchedMigrationJob

#batch_metrics, cursor, cursor?, cursor_columns, feature_category, #filter_batch, generic_instance, health_context_tables, #initialize, job_arguments, job_arguments_count, operation_name, scope_to, tables_to_check_for_vacuum

Methods included from Database::DynamicModelHelpers

define_batchable_model, #each_batch, #each_batch_range

Constructor Details

This class inherits a constructor from Gitlab::BackgroundMigration::BatchedMigrationJob

Instance Method Details

#affected_transition(vulnerability) ⇒ Object



117
118
119
120
121
122
123
124
125
126
# File 'lib/gitlab/background_migration/fix_vulnerabilities_transitioned_from_dismissed_to_resolved.rb', line 117

def affected_transition(vulnerability)
  vulnerability.state_transitions.find do |state_transition|
    break if state_transition.created_before_issue_first_appeared?
    # If the state has been transitioned by someone besides the security policy bot then we should
    # respect their decision. When a vulnerability is redetected by a scanner, the transition has no author.
    break if state_transition.author.present? && !state_transition.author.security_policy_bot?

    state_transition.transitioned_from_dismissed_to_resolved?
  end
end

#affected_vulnerability_data(vulnerability_reads) ⇒ Object



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/gitlab/background_migration/fix_vulnerabilities_transitioned_from_dismissed_to_resolved.rb', line 99

def affected_vulnerability_data(vulnerability_reads)
  Vulnerability
    .id_in(vulnerability_reads.pluck(:vulnerability_id))
    .transitioned_at_least_once
    .with_state_transitions_author_and_project
    .filter_map do |vulnerability|
      bug_transition = affected_transition(vulnerability)

      next if bug_transition.blank?

      {
        vulnerability: vulnerability,
        bug_transition: bug_transition,
        original_dismissal_transition: original_dismissal(vulnerability, bug_transition)
      }
    end
end

#connectionObject



179
180
181
# File 'lib/gitlab/background_migration/fix_vulnerabilities_transitioned_from_dismissed_to_resolved.rb', line 179

def connection
  ::SecApplicationRecord.connection
end

#insert_notes(data, timestamp) ⇒ Object



158
159
160
# File 'lib/gitlab/background_migration/fix_vulnerabilities_transitioned_from_dismissed_to_resolved.rb', line 158

def insert_notes(data, timestamp)
  Note.insert_all(note_attributes(data, timestamp))
end

#note_attributes(data, timestamp) ⇒ Object



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/gitlab/background_migration/fix_vulnerabilities_transitioned_from_dismissed_to_resolved.rb', line 162

def note_attributes(data, timestamp)
  data.map do |record|
    vulnerability = record[:vulnerability]
    {
      noteable_type: "Vulnerability",
      noteable_id: vulnerability.id,
      project_id: vulnerability.project.id,
      namespace_id: vulnerability.project.project_namespace_id,
      system: true,
      note: COMMENT,
      author_id: record[:bug_transition].author_id,
      created_at: timestamp,
      updated_at: timestamp
    }
  end
end

#original_dismissal(vulnerability, bug_transition) ⇒ Object



128
129
130
131
132
# File 'lib/gitlab/background_migration/fix_vulnerabilities_transitioned_from_dismissed_to_resolved.rb', line 128

def original_dismissal(vulnerability, bug_transition)
  bug_transition_index = vulnerability.state_transitions.index(bug_transition)
  prior_transitions = vulnerability.state_transitions[(bug_transition_index + 1)..]
  prior_transitions.find { |transition| transition.to_state == Vulnerability.states[:dismissed] }
end

#performObject



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/gitlab/background_migration/fix_vulnerabilities_transitioned_from_dismissed_to_resolved.rb', line 72

def perform
  each_sub_batch do |batch|
    vulnerability_reads = scoped_vulnerability_reads(batch)

    next if vulnerability_reads.blank?

    data = affected_vulnerability_data(vulnerability_reads)

    next if data.blank?

    batch_timestamp = Time.current

    transition_states(data, batch_timestamp)
    insert_notes(data, batch_timestamp)
  end
end

#scoped_vulnerability_reads(vulnerability_reads) ⇒ Object



89
90
91
92
93
94
95
96
97
# File 'lib/gitlab/background_migration/fix_vulnerabilities_transitioned_from_dismissed_to_resolved.rb', line 89

def scoped_vulnerability_reads(vulnerability_reads)
  relation = vulnerability_reads.where(state: [Vulnerability.states[:detected], Vulnerability.states[:resolved]])

  return relation if namespace_id == 'instance'

  relation
    .where(vulnerability_reads.arel_table[:traversal_ids].gteq([namespace_id]))
    .where(vulnerability_reads.arel_table[:traversal_ids].lt([namespace_id.next]))
end

#state_transition_attributes(data, timestamp) ⇒ Object



143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/gitlab/background_migration/fix_vulnerabilities_transitioned_from_dismissed_to_resolved.rb', line 143

def state_transition_attributes(data, timestamp)
  data.map do |record|
    {
      author_id: record[:bug_transition].author_id,
      from_state: Vulnerability.states[record[:vulnerability].state],
      to_state: Vulnerability.states[:dismissed],
      dismissal_reason: record[:original_dismissal_transition]&.dismissal_reason || 0,
      vulnerability_id: record[:vulnerability].id,
      comment: COMMENT,
      created_at: timestamp,
      updated_at: timestamp
    }
  end
end

#transition_states(data, timestamp) ⇒ Object



134
135
136
137
138
139
140
141
# File 'lib/gitlab/background_migration/fix_vulnerabilities_transitioned_from_dismissed_to_resolved.rb', line 134

def transition_states(data, timestamp)
  vulnerability_ids = data.map { |record| record[:vulnerability].id }

  Vulnerability.transaction do
    Vulnerability.id_in(vulnerability_ids).update_all(state: :dismissed, updated_at: timestamp)
    StateTransition.insert_all(state_transition_attributes(data, timestamp))
  end
end