From 769a2e7fa954e7120435e977b919f4a9451bff05 Mon Sep 17 00:00:00 2001
From: Igor Frenkel <ifrenkel@gitlab.com>
Date: Mon, 31 Jul 2023 09:22:14 +0000
Subject: [PATCH] Add versions attribute to affected packages

Add support for the optional `versions` attribute used in
the Gitlab Advisory Database to describe golang pseudoversions.

Changelog: added
---
 ...ffected_packages_add_versions_attribute.rb |  9 +++
 db/schema_migrations/20230724185321           |  1 +
 db/structure.sql                              |  1 +
 .../package_metadata/affected_package.rb      |  1 +
 .../pm_affected_package_versions.json         | 71 +++++++++++++++++++
 .../package_metadata/affected_package_spec.rb | 36 ++++++++++
 6 files changed, 119 insertions(+)
 create mode 100644 db/migrate/20230724185321_pm_affected_packages_add_versions_attribute.rb
 create mode 100644 db/schema_migrations/20230724185321
 create mode 100644 ee/app/validators/json_schemas/pm_affected_package_versions.json

diff --git a/db/migrate/20230724185321_pm_affected_packages_add_versions_attribute.rb b/db/migrate/20230724185321_pm_affected_packages_add_versions_attribute.rb
new file mode 100644
index 0000000000000..f1adb65324bc4
--- /dev/null
+++ b/db/migrate/20230724185321_pm_affected_packages_add_versions_attribute.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class PmAffectedPackagesAddVersionsAttribute < Gitlab::Database::Migration[2.1]
+  enable_lock_retries!
+
+  def change
+    add_column :pm_affected_packages, :versions, :jsonb, default: [], null: false
+  end
+end
diff --git a/db/schema_migrations/20230724185321 b/db/schema_migrations/20230724185321
new file mode 100644
index 0000000000000..b5ffbeb573425
--- /dev/null
+++ b/db/schema_migrations/20230724185321
@@ -0,0 +1 @@
+73dc6e44071a82ff0e6237eaf8619af20944ecb959de803779372138d63ee194
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 93e3ad40756bb..97e260e30e191 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -20427,6 +20427,7 @@ CREATE TABLE pm_affected_packages (
     affected_range text NOT NULL,
     fixed_versions text[] DEFAULT '{}'::text[],
     overridden_advisory_fields jsonb DEFAULT '{}'::jsonb NOT NULL,
+    versions jsonb DEFAULT '[]'::jsonb NOT NULL,
     CONSTRAINT check_5dd528a2be CHECK ((char_length(package_name) <= 256)),
     CONSTRAINT check_80dea16c7b CHECK ((char_length(affected_range) <= 512)),
     CONSTRAINT check_d1d4646298 CHECK ((char_length(solution) <= 2048)),
diff --git a/ee/app/models/package_metadata/affected_package.rb b/ee/app/models/package_metadata/affected_package.rb
index 4963e31f93f6d..0095112a8468b 100644
--- a/ee/app/models/package_metadata/affected_package.rb
+++ b/ee/app/models/package_metadata/affected_package.rb
@@ -17,5 +17,6 @@ class AffectedPackage < ApplicationRecord
     validates :affected_range, presence: true, length: { maximum: 512 }
     validates :overridden_advisory_fields, json_schema: { filename: 'pm_affected_package_overridden_advisory_fields' }
     validates :fixed_versions, length: { maximum: 10 }
+    validates :versions, json_schema: { filename: 'pm_affected_package_versions' }
   end
 end
diff --git a/ee/app/validators/json_schemas/pm_affected_package_versions.json b/ee/app/validators/json_schemas/pm_affected_package_versions.json
new file mode 100644
index 0000000000000..d51d870151d42
--- /dev/null
+++ b/ee/app/validators/json_schemas/pm_affected_package_versions.json
@@ -0,0 +1,71 @@
+{
+  "$id": "#/properties/versions",
+  "type": "array",
+  "title": "Version Meta Information",
+  "minItems": 0,
+  "maxItems": 32,
+  "items": {
+    "$id": "#/properties/versions/items",
+    "type": "object",
+    "required": [
+      "number",
+      "commit"
+    ],
+    "properties": {
+      "number": {
+        "$id": "#/properties/versions/items/properties/number",
+        "type": "string",
+        "title": "Version Information.",
+        "pattern": "^([\\d\\.a-zA-Z_\\-]{1,32})$",
+        "examples": [
+          "1.2.3"
+        ]
+      },
+      "commit": {
+        "$id": "#/properties/versions/items/properties/commit",
+        "type": "object",
+        "title": "Git commit meta information.",
+        "required": [
+          "tags",
+          "sha",
+          "timestamp"
+        ],
+        "properties": {
+          "tags": {
+            "$id": "#/properties/versions/items/properties/commit/tags",
+            "type": "array",
+            "title": "Array of Git Tags associated with this particular version.",
+            "minItems": 0,
+            "maxItems": 16,
+            "items": {
+              "$id": "#/properties/versions/items/properties/commit/tags/items",
+              "type": "string",
+              "examples": [
+                "v1.2.3-tag"
+              ],
+              "pattern": "^[a-zA-Z0-9_\\-\\./]{0,32}$"
+            }
+          },
+          "sha": {
+            "$id": "#/properties/versions/items/properties/commit/sha",
+            "type": "string",
+            "title": "Git commit sha.",
+            "pattern": "^[0-9a-f]{5,40}$",
+            "examples": [
+              "295cf0778821bf08681e2bd0ef0e6cad04fc3001"
+            ]
+          },
+          "timestamp": {
+            "$id": "#/properties/versions/items/properties/commit/timestamp",
+            "type": "string",
+            "title": "Timestamp of the format YYYYMMDDHHMMSS.",
+            "pattern": "^\\d{14,14}$",
+            "examples": [
+              "20190626162700"
+            ]
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/ee/spec/models/package_metadata/affected_package_spec.rb b/ee/spec/models/package_metadata/affected_package_spec.rb
index 3d6bc431e06c2..d14490333c1ab 100644
--- a/ee/spec/models/package_metadata/affected_package_spec.rb
+++ b/ee/spec/models/package_metadata/affected_package_spec.rb
@@ -78,5 +78,41 @@
         end
       end
     end
+
+    describe 'versions' do
+      subject { build(:pm_affected_package, versions: versions).valid? }
+
+      let(:valid_sha) { '295cf0778821bf08681e2bd0ef0e6cad04fc3001' }
+      let(:valid_timestamp) { '20190626162700' }
+      let(:valid_number) { '1.2.3' }
+      let(:valid_tags) { ['v1.2.3-tag'] }
+      let(:valid_num_versions) { 1 }
+
+      # rubocop:disable Layout/LineLength
+      where(:number, :tags, :sha, :timestamp, :num_versions, :is_valid) do
+        ref(:valid_number) | ref(:valid_tags) | ref(:valid_sha) | ref(:valid_timestamp) | 0                        | true
+        ref(:valid_number) | ref(:valid_tags) | ref(:valid_sha) | ref(:valid_timestamp) | 32                       | true
+        ref(:valid_number) | ref(:valid_tags) | ref(:valid_sha) | ref(:valid_timestamp) | 33                       | false
+        ref(:valid_number) | ['']             | ref(:valid_sha) | ref(:valid_timestamp) | ref(:valid_num_versions) | true
+        ref(:valid_number) | [('a' * 32)]     | ref(:valid_sha) | ref(:valid_timestamp) | ref(:valid_num_versions) | true
+        ref(:valid_number) | []               | ref(:valid_sha) | ref(:valid_timestamp) | ref(:valid_num_versions) | true
+        ref(:valid_number) | (['a'] * 16)     | ref(:valid_sha) | ref(:valid_timestamp) | ref(:valid_num_versions) | true
+        ref(:valid_number) | (['a'] * 17)     | ref(:valid_sha) | ref(:valid_timestamp) | ref(:valid_num_versions) | false
+        ref(:valid_number) | [('a' * 33)]     | ref(:valid_sha) | ref(:valid_timestamp) | ref(:valid_num_versions) | false
+        ''                 | ref(:valid_tags) | ref(:valid_sha) | ref(:valid_timestamp) | ref(:valid_num_versions) | false
+        ref(:valid_number) | ref(:valid_tags) | ('a' * 4)       | ref(:valid_timestamp) | ref(:valid_num_versions) | false
+        ref(:valid_number) | ref(:valid_tags) | ('a' * 41)      | ref(:valid_timestamp) | ref(:valid_num_versions) | false
+        ref(:valid_number) | ref(:valid_tags) | ref(:valid_sha) | '2019062616270'       | ref(:valid_num_versions) | false
+        ref(:valid_number) | ref(:valid_tags) | ref(:valid_sha) | '2019062616270a'      | ref(:valid_num_versions) | false
+        ref(:valid_number) | ref(:valid_tags) | ref(:valid_sha) | '201906261627001'     | ref(:valid_num_versions) | false
+      end
+      # rubocop:enable Layout/LineLength
+
+      with_them do
+        let(:versions) { [{ number: number, commit: { tags: tags, sha: sha, timestamp: timestamp } }] * num_versions }
+
+        it { is_expected.to eq(is_valid) }
+      end
+    end
   end
 end
-- 
GitLab