From 763dae9e6b0848fb2066f458c64905c6e374dfe7 Mon Sep 17 00:00:00 2001
From: ghinfey <ghinfey@gitlab.com>
Date: Tue, 18 Feb 2025 14:13:42 +0000
Subject: [PATCH] Add unverified_author_email to grahql endpoint

Add unverified_author_email to the graphql verification
status field.

Update front end to consume new field to match how
templating currently handles it.

Changelog: fixed
MR: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181805
---
 .../commit/components/signature_badge.vue     |  2 +-
 app/assets/javascripts/commit/constants.js    | 14 +++++++++++
 .../types/commit_signature_interface.rb       |  3 ++-
 app/models/concerns/commit_signature.rb       | 13 ++++++----
 app/models/concerns/enums/commit_signature.rb |  3 ++-
 .../projects/commit/_signature.html.haml      |  2 +-
 doc/api/graphql/reference/_index.md           |  1 +
 lib/gitlab/x509/signature.rb                  |  1 -
 .../commit/components/signature_badge_spec.js |  3 +++
 .../gpg/invalid_gpg_signature_updater_spec.rb |  3 ++-
 spec/lib/gitlab/ssh/commit_spec.rb            |  4 ++++
 .../commit_signatures/gpg_signature_spec.rb   | 24 +++++++++----------
 .../commit_signatures/ssh_signature_spec.rb   | 20 ++++++++--------
 .../x509_commit_signature_spec.rb             | 14 +++++------
 14 files changed, 67 insertions(+), 40 deletions(-)

diff --git a/app/assets/javascripts/commit/components/signature_badge.vue b/app/assets/javascripts/commit/components/signature_badge.vue
index 0ec828fa2082..70b89d6ca06f 100644
--- a/app/assets/javascripts/commit/components/signature_badge.vue
+++ b/app/assets/javascripts/commit/components/signature_badge.vue
@@ -51,7 +51,7 @@ export default {
       class="gl-border-0 gl-bg-transparent gl-p-0 gl-outline-none"
       :aria-label="statusConfig.label"
     >
-      <gl-badge :variant="statusConfig.variant">
+      <gl-badge :icon="statusConfig.icon" :variant="statusConfig.variant">
         {{ statusConfig.label }}
       </gl-badge>
     </button>
diff --git a/app/assets/javascripts/commit/constants.js b/app/assets/javascripts/commit/constants.js
index 417a0e2b6d00..fb68c118cfcb 100644
--- a/app/assets/javascripts/commit/constants.js
+++ b/app/assets/javascripts/commit/constants.js
@@ -12,6 +12,7 @@ export const verificationStatuses = {
   MULTIPLE_SIGNATURES: 'MULTIPLE_SIGNATURES',
   REVOKED_KEY: 'REVOKED_KEY',
   VERIFIED_SYSTEM: 'VERIFIED_SYSTEM',
+  UNVERIFIED_AUTHOR_EMAIL: 'UNVERIFIED_AUTHOR_EMAIL',
 };
 
 export const signatureTypes = {
@@ -29,6 +30,13 @@ const UNVERIFIED_CONFIG = {
   description: __('This commit was signed with an unverified signature.'),
 };
 
+export const REVERIFIED_CONFIG = {
+  variant: 'warning',
+  icon: 'warning',
+  label: __('Verified'),
+  title: __('Verified commit with unverified email'),
+};
+
 export const VERIFIED_CONFIG = {
   variant: 'success',
   label: __('Verified'),
@@ -42,6 +50,12 @@ export const statusConfig = {
       'This commit was signed with a verified signature and the committer email was verified to belong to the same user.',
     ),
   },
+  [verificationStatuses.UNVERIFIED_AUTHOR_EMAIL]: {
+    ...REVERIFIED_CONFIG,
+    description: __(
+      'This commit was previously signed with a verified signature and verified committer email address. However the committer email address is no longer verified to the same user.',
+    ),
+  },
   [verificationStatuses.VERIFIED_SYSTEM]: {
     ...VERIFIED_CONFIG,
     description: __(
diff --git a/app/graphql/types/commit_signature_interface.rb b/app/graphql/types/commit_signature_interface.rb
index 87ed6ea4ff46..df138fdb1290 100644
--- a/app/graphql/types/commit_signature_interface.rb
+++ b/app/graphql/types/commit_signature_interface.rb
@@ -10,7 +10,8 @@ module CommitSignatureInterface
 
     field :verification_status, CommitSignatures::VerificationStatusEnum,
       null: true,
-      description: 'Indicates verification status of the associated key or certificate.'
+      description: 'Indicates verification status of the associated key or certificate.',
+      calls_gitaly: true
 
     field :commit_sha, GraphQL::Types::String,
       null: true,
diff --git a/app/models/concerns/commit_signature.rb b/app/models/concerns/commit_signature.rb
index e4260edae409..31216b1fc9d0 100644
--- a/app/models/concerns/commit_signature.rb
+++ b/app/models/concerns/commit_signature.rb
@@ -42,13 +42,16 @@ def signed_by_user
     raise NoMethodError, 'must implement `signed_by_user` method'
   end
 
-  def reverified_status
-    return verification_status unless Feature.enabled?(:check_for_mailmapped_commit_emails, project)
-    return verification_status unless verified? || verified_system?
+  # If commit is persisted as verified, check that commit email is still correct.
+  def verification_status
+    persisted_status = read_attribute(:verification_status)
+    return persisted_status unless Feature.enabled?(:check_for_mailmapped_commit_emails, project)
+    return persisted_status unless verified? || verified_system?
+    return persisted_status unless commit
 
-    return 'unverified_author_email' if emails_for_verification&.exclude?(commit&.author_email)
+    return 'unverified_author_email' if emails_for_verification&.exclude?(commit.author_email)
 
-    verification_status
+    persisted_status
   end
 
   private
diff --git a/app/models/concerns/enums/commit_signature.rb b/app/models/concerns/enums/commit_signature.rb
index 92625af58ef8..0c6b28bbef96 100644
--- a/app/models/concerns/enums/commit_signature.rb
+++ b/app/models/concerns/enums/commit_signature.rb
@@ -11,7 +11,8 @@ class CommitSignature
       unknown_key: 5,
       multiple_signatures: 6,
       revoked_key: 7,
-      verified_system: 8
+      verified_system: 8,
+      unverified_author_email: 9
       # EE adds more values in ee/app/models/concerns/ee/enums/commit_signature.rb
     }.freeze
 
diff --git a/app/views/projects/commit/_signature.html.haml b/app/views/projects/commit/_signature.html.haml
index c21387758dc6..e384a4724f95 100644
--- a/app/views/projects/commit/_signature.html.haml
+++ b/app/views/projects/commit/_signature.html.haml
@@ -1,5 +1,5 @@
 - if signature
   - uri = "projects/commit/#{'x509/' if signature.x509?}"
-  = render partial: "#{uri}#{signature.reverified_status}_signature_badge", locals: { signature: signature }
+  = render partial: "#{uri}#{signature.verification_status}_signature_badge", locals: { signature: signature }
 - else
   = render partial: 'projects/commit/unverified_signature_badge', locals: { signature: nil }
diff --git a/doc/api/graphql/reference/_index.md b/doc/api/graphql/reference/_index.md
index d406a5e02b43..4796e64a7a81 100644
--- a/doc/api/graphql/reference/_index.md
+++ b/doc/api/graphql/reference/_index.md
@@ -43917,6 +43917,7 @@ Verification status of a GPG, X.509 or SSH signature for a commit.
 | <a id="verificationstatussame_user_different_email"></a>`SAME_USER_DIFFERENT_EMAIL` | same_user_different_email verification status. |
 | <a id="verificationstatusunknown_key"></a>`UNKNOWN_KEY` | unknown_key verification status. |
 | <a id="verificationstatusunverified"></a>`UNVERIFIED` | unverified verification status. |
+| <a id="verificationstatusunverified_author_email"></a>`UNVERIFIED_AUTHOR_EMAIL` | unverified_author_email verification status. |
 | <a id="verificationstatusunverified_key"></a>`UNVERIFIED_KEY` | unverified_key verification status. |
 | <a id="verificationstatusverified"></a>`VERIFIED` | verified verification status. |
 | <a id="verificationstatusverified_ca"></a>`VERIFIED_CA` | verified_ca verification status. |
diff --git a/lib/gitlab/x509/signature.rb b/lib/gitlab/x509/signature.rb
index 1f96b95716bc..0f9d40440a28 100644
--- a/lib/gitlab/x509/signature.rb
+++ b/lib/gitlab/x509/signature.rb
@@ -52,7 +52,6 @@ def verification_status
 
         :unverified
       end
-      alias_method :reverified_status, :verification_status
 
       private
 
diff --git a/spec/frontend/commit/components/signature_badge_spec.js b/spec/frontend/commit/components/signature_badge_spec.js
index 4e8ad8e12f12..ae5f879f470d 100644
--- a/spec/frontend/commit/components/signature_badge_spec.js
+++ b/spec/frontend/commit/components/signature_badge_spec.js
@@ -44,9 +44,12 @@ describe('Commit signature', () => {
     ${signatureTypes.GPG}  | ${verificationStatuses.OTHER_USER}
     ${signatureTypes.GPG}  | ${verificationStatuses.SAME_USER_DIFFERENT_EMAIL}
     ${signatureTypes.GPG}  | ${verificationStatuses.MULTIPLE_SIGNATURES}
+    ${signatureTypes.GPG}  | ${verificationStatuses.UNVERIFIED_AUTHOR_EMAIL}
     ${signatureTypes.X509} | ${verificationStatuses.VERIFIED}
+    ${signatureTypes.X509} | ${verificationStatuses.UNVERIFIED_AUTHOR_EMAIL}
     ${signatureTypes.SSH}  | ${verificationStatuses.VERIFIED}
     ${signatureTypes.SSH}  | ${verificationStatuses.REVOKED_KEY}
+    ${signatureTypes.SSH}  | ${verificationStatuses.UNVERIFIED_AUTHOR_EMAIL}
   `(
     'For a specified `$signatureType` and `$verificationStatus` it renders component correctly',
     ({ signatureType, verificationStatus }) => {
diff --git a/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb b/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb
index db88e99970c2..5c3410056fbb 100644
--- a/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb
+++ b/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb
@@ -19,7 +19,8 @@
         :raw_commit,
         signature: signature,
         sha: commit_sha,
-        committer_email: committer_email
+        committer_email: committer_email,
+        author_email: committer_email
       )
 
       allow(raw_commit).to receive :save!
diff --git a/spec/lib/gitlab/ssh/commit_spec.rb b/spec/lib/gitlab/ssh/commit_spec.rb
index 25aa92526301..79393c7f8bd5 100644
--- a/spec/lib/gitlab/ssh/commit_spec.rb
+++ b/spec/lib/gitlab/ssh/commit_spec.rb
@@ -26,6 +26,10 @@
       .with(Gitlab::Git::Repository, commit.sha)
       .and_return(signature_data)
 
+    allow_next_instance_of(Commit) do |instance|
+      allow(instance).to receive(:author_email).and_return(user_author.email)
+    end
+
     allow(verifier).to receive_messages({
       verification_status: verification_status,
       signed_by_key: signed_by_key,
diff --git a/spec/models/commit_signatures/gpg_signature_spec.rb b/spec/models/commit_signatures/gpg_signature_spec.rb
index 5f27212ac33c..a338964ca681 100644
--- a/spec/models/commit_signatures/gpg_signature_spec.rb
+++ b/spec/models/commit_signatures/gpg_signature_spec.rb
@@ -116,7 +116,7 @@
     end
   end
 
-  describe '#reverified_status' do
+  describe '#verification_status' do
     let(:verification_status) { :verified }
     let(:signature) do
       create(:gpg_signature, commit_sha: commit_sha, gpg_key: gpg_key, project: project,
@@ -128,16 +128,16 @@
     end
 
     # verified is used for user signed gpg commits.
-    context 'when verification_status is verified' do
-      it 'returns existing verification status' do
-        expect(signature.reverified_status).to eq('verified')
+    context 'when persisted verification_status is verified' do
+      it 'returns persisted verification status' do
+        expect(signature.verification_status).to eq('verified')
       end
 
       context 'when commit author does not match the gpg_key author' do
         let(:commit_author) { create(:user) }
 
         it 'returns unverified_author_email' do
-          expect(signature.reverified_status).to eq('unverified_author_email')
+          expect(signature.verification_status).to eq('unverified_author_email')
         end
 
         context 'when check_for_mailmapped_commit_emails feature flag is disabled' do
@@ -146,33 +146,33 @@
           end
 
           it 'verification status is unmodified' do
-            expect(signature.reverified_status).to eq('verified')
+            expect(signature.verification_status).to eq('verified')
           end
         end
       end
     end
 
-    context 'when verification_status not verified' do
+    context 'when persisted verification_status not verified' do
       let(:verification_status) { :unverified }
 
       it 'returns the signature verification status' do
-        expect(signature.reverified_status).to eq('unverified')
+        expect(signature.verification_status).to eq('unverified')
       end
     end
 
     # verified_system is used for ui signed commits.
-    context 'when verification_status is verified_system' do
+    context 'when persisted verification_status is verified_system' do
       let(:verification_status) { :verified_system }
 
       it 'returns existing verification status' do
-        expect(signature.reverified_status).to eq('verified_system')
+        expect(signature.verification_status).to eq('verified_system')
       end
 
       context 'when commit author does not match the gpg_key author' do
         let(:commit_author) { create(:user) }
 
-        it 'returns existing verification status' do
-          expect(signature.reverified_status).to eq('unverified_author_email')
+        it 'returns unverified_author_email' do
+          expect(signature.verification_status).to eq('unverified_author_email')
         end
       end
     end
diff --git a/spec/models/commit_signatures/ssh_signature_spec.rb b/spec/models/commit_signatures/ssh_signature_spec.rb
index 55e0ef20aee7..e6061d1703da 100644
--- a/spec/models/commit_signatures/ssh_signature_spec.rb
+++ b/spec/models/commit_signatures/ssh_signature_spec.rb
@@ -76,21 +76,21 @@
     end
   end
 
-  describe '#reverified_status' do
+  describe '#verification_status' do
     before do
       allow(signature.project).to receive(:commit).with(commit_sha).and_return(commit)
     end
 
-    context 'when verification_status is verified' do
+    context 'when persisted verification_status is verified' do
       it 'returns verified' do
-        expect(signature.reverified_status).to eq('verified')
+        expect(signature.verification_status).to eq('verified')
       end
 
       context 'and the author email does not belong to the signed by user' do
         let(:user) { create(:user) }
 
         it 'returns unverified_author_email' do
-          expect(signature.reverified_status).to eq('unverified_author_email')
+          expect(signature.verification_status).to eq('unverified_author_email')
         end
 
         context 'when check_for_mailmapped_commit_emails feature flag is disabled' do
@@ -99,17 +99,17 @@
           end
 
           it 'verification status is unmodified' do
-            expect(signature.reverified_status).to eq('verified')
+            expect(signature.verification_status).to eq('verified')
           end
         end
       end
     end
 
-    context 'when verification_status not verified' do
+    context 'when persisted verification_status not verified' do
       let(:signature) { create(:ssh_signature, verification_status: 'unverified') }
 
       it 'returns the signature verification status' do
-        expect(signature.reverified_status).to eq('unverified')
+        expect(signature.verification_status).to eq('unverified')
       end
     end
 
@@ -117,14 +117,14 @@
       let(:verification_status) { :verified_system }
 
       it 'returns the signature verification status' do
-        expect(signature.reverified_status).to eq('verified_system')
+        expect(signature.verification_status).to eq('verified_system')
       end
 
       context 'and the author email does not belong to the signed by user' do
         let(:user) { create(:user) }
 
         it 'returns unverified_author_email' do
-          expect(signature.reverified_status).to eq('unverified_author_email')
+          expect(signature.verification_status).to eq('unverified_author_email')
         end
 
         context 'when check_for_mailmapped_commit_emails feature flag is disabled' do
@@ -133,7 +133,7 @@
           end
 
           it 'verification status is unmodified' do
-            expect(signature.reverified_status).to eq('verified_system')
+            expect(signature.verification_status).to eq('verified_system')
           end
         end
       end
diff --git a/spec/models/commit_signatures/x509_commit_signature_spec.rb b/spec/models/commit_signatures/x509_commit_signature_spec.rb
index 24a49e5bdb82..b287c344747c 100644
--- a/spec/models/commit_signatures/x509_commit_signature_spec.rb
+++ b/spec/models/commit_signatures/x509_commit_signature_spec.rb
@@ -9,7 +9,7 @@
   let_it_be(:commit_sha) { '189a6c924013fc3fe40d6f1ec1dc20214183bc97' }
   let_it_be(:project) { create(:project, :public, :repository) }
   let_it_be(:commit) { create(:commit, project: project, sha: commit_sha) }
-  let_it_be(:x509_certificate) { create(:x509_certificate) }
+  let_it_be(:x509_certificate) { create(:x509_certificate, email: 'r.meier@siemens.com') }
   let_it_be(:verification_status) { "verified" }
 
   let(:attributes) do
@@ -49,16 +49,16 @@
     end
   end
 
-  describe '#reverified_status' do
+  describe '#verification_status' do
     let_it_be(:matching_email) { 'r.meier@siemens.com' }
 
-    subject(:reverified_status) { described_class.safe_create!(attributes).reverified_status }
+    subject(:signature) { described_class.safe_create!(attributes) }
 
     context 'when the commit email matches the x509 certificate emails' do
       let_it_be(:x509_certificate) { create(:x509_certificate, email: matching_email) }
 
       it 'returns verified' do
-        expect(reverified_status).to eq('verified')
+        expect(signature.verification_status).to eq('verified')
       end
     end
 
@@ -71,7 +71,7 @@
       end
 
       it 'returns verified' do
-        expect(reverified_status).to eq('verified')
+        expect(signature.verification_status).to eq('verified')
       end
     end
 
@@ -84,7 +84,7 @@
       end
 
       it 'returns unverified_author_email' do
-        expect(reverified_status).to eq('unverified_author_email')
+        expect(signature.verification_status).to eq('unverified_author_email')
       end
     end
 
@@ -94,7 +94,7 @@
       end
 
       it 'verification status is unmodified' do
-        expect(reverified_status).to eq('verified')
+        expect(signature.verification_status).to eq('verified')
       end
     end
   end
-- 
GitLab