From dba8992cad989ff4717058a1c945db8a2b1f3d0a Mon Sep 17 00:00:00 2001
From: Carla Drago <cdrago@gitlab.com>
Date: Wed, 26 Jun 2024 15:41:43 +0000
Subject: [PATCH] Add jwt for jira connect branches

This adds a query param to the create branch url of the
GitLab for Jira app descriptor. It updates the Jira Connect

branches controller to handle redirections to
self-managed gitlab instances when a jwt is present
in the params.

Changelog: fixed
---
 .../jira_connect/app_descriptor_controller.rb |  6 +-
 .../jira_connect/branches_controller.rb       | 22 +++++--
 app/models/jira_connect_installation.rb       |  6 ++
 .../jira_connect_proxy_create_branch.yml      |  9 +++
 config/routes/jira_connect.rb                 |  6 +-
 doc/administration/settings/jira_cloud_app.md |  2 +-
 .../app_descriptor_controller_spec.rb         |  2 +-
 .../jira_connect/branches_controller_spec.rb  | 58 +++++++++++++++++++
 spec/models/jira_connect_installation_spec.rb | 18 ++++++
 9 files changed, 121 insertions(+), 8 deletions(-)
 create mode 100644 config/feature_flags/gitlab_com_derisk/jira_connect_proxy_create_branch.yml

diff --git a/app/controllers/jira_connect/app_descriptor_controller.rb b/app/controllers/jira_connect/app_descriptor_controller.rb
index 2e0949a584e89..ea1678ba8f3cc 100644
--- a/app/controllers/jira_connect/app_descriptor_controller.rb
+++ b/app/controllers/jira_connect/app_descriptor_controller.rb
@@ -130,10 +130,14 @@ def relative_to_base_path(full_path)
     full_path.sub(/^#{jira_connect_base_path}/, '')
   end
 
+  def create_branch_params
+    "?issue_key={issue.key}&issue_summary={issue.summary}&jwt={jwt}&addonkey=#{Atlassian::JiraConnect.app_key}"
+  end
+
   def actions
     {
       createBranch: {
-        templateUrl: "#{new_jira_connect_branch_url}?issue_key={issue.key}&issue_summary={issue.summary}"
+        templateUrl: "#{route_jira_connect_branches_url}#{create_branch_params}"
       },
       searchConnectedWorkspaces: {
         templateUrl: search_jira_connect_workspaces_url
diff --git a/app/controllers/jira_connect/branches_controller.rb b/app/controllers/jira_connect/branches_controller.rb
index 4c1b0d2b208a9..6f57805d23cb2 100644
--- a/app/controllers/jira_connect/branches_controller.rb
+++ b/app/controllers/jira_connect/branches_controller.rb
@@ -1,14 +1,28 @@
 # frozen_string_literal: true
 
-# NOTE: This controller does not inherit from JiraConnect::ApplicationController
-# because we don't receive a JWT for this action, so we rely on standard GitLab authentication.
-class JiraConnect::BranchesController < ApplicationController
-  feature_category :integrations
+class JiraConnect::BranchesController < JiraConnect::ApplicationController
+  # before_action :authenticate_user!, only: :new
+  skip_before_action :verify_atlassian_jwt!, only: :new
 
   def new
+    # move authenticate_user! to a before_action when we remove the jira_connect_proxy_create_branch feature flag
+    authenticate_user! if Feature.enabled?(:jira_connect_proxy_create_branch, current_user)
+
     @new_branch_data = new_branch_data
   end
 
+  # If the GitLab for Jira Cloud app was installed from the Jira marketplace and points to a self-managed instance,
+  # we route the user to the self-managed instance, otherwise we redirect to :new
+  def route
+    if Feature.enabled?(:jira_connect_proxy_create_branch, current_user) && current_jira_installation.proxy?
+      redirect_to "#{current_jira_installation.create_branch_url}?#{request.query_string}"
+
+      return
+    end
+
+    redirect_to "#{new_jira_connect_branch_path}?#{request.query_string}"
+  end
+
   private
 
   def initial_branch_name
diff --git a/app/models/jira_connect_installation.rb b/app/models/jira_connect_installation.rb
index 915342486c5b6..0da8482fea4f7 100644
--- a/app/models/jira_connect_installation.rb
+++ b/app/models/jira_connect_installation.rb
@@ -54,6 +54,12 @@ def audience_uninstalled_event_url
     Gitlab::Utils.append_path(instance_url, jira_connect_events_uninstalled_path)
   end
 
+  def create_branch_url
+    return unless proxy?
+
+    Gitlab::Utils.append_path(instance_url, new_jira_connect_branch_path)
+  end
+
   def proxy?
     instance_url.present?
   end
diff --git a/config/feature_flags/gitlab_com_derisk/jira_connect_proxy_create_branch.yml b/config/feature_flags/gitlab_com_derisk/jira_connect_proxy_create_branch.yml
new file mode 100644
index 0000000000000..8742727f4e067
--- /dev/null
+++ b/config/feature_flags/gitlab_com_derisk/jira_connect_proxy_create_branch.yml
@@ -0,0 +1,9 @@
+---
+name: jira_connect_proxy_create_branch
+feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/391432
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/149377
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/466462
+milestone: '17.2'
+group: group::import and integrate
+type: gitlab_com_derisk
+default_enabled: false
diff --git a/config/routes/jira_connect.rb b/config/routes/jira_connect.rb
index 3720fb69b95c1..39276a5cb5388 100644
--- a/config/routes/jira_connect.rb
+++ b/config/routes/jira_connect.rb
@@ -12,7 +12,11 @@
   end
 
   resources :subscriptions, only: [:index, :create, :destroy]
-  resources :branches, only: [:new]
+  resources :branches, only: [:new] do
+    collection do
+      get :route
+    end
+  end
   resources :public_keys, only: :show
 
   resources :workspaces, only: [] do
diff --git a/doc/administration/settings/jira_cloud_app.md b/doc/administration/settings/jira_cloud_app.md
index 4c27e7caf8856..7d2ec1ec19c5a 100644
--- a/doc/administration/settings/jira_cloud_app.md
+++ b/doc/administration/settings/jira_cloud_app.md
@@ -81,7 +81,7 @@ You can use the official GitLab for Jira Cloud app from the Atlassian Marketplac
 With this method:
 
 - GitLab.com [handles the install and uninstall lifecycle events](#gitlabcom-handling-of-app-lifecycle-events) sent from Jira Cloud and forwards them to your GitLab instance. All data from your self-managed instance is still sent directly to Jira Cloud.
-- It's not possible to create branches from Jira Cloud.
+- With any version of GitLab prior to 17.2 it is not possible to create branches from Jira Cloud on self-managed instances.
   For more information, see [issue 391432](https://gitlab.com/gitlab-org/gitlab/-/issues/391432).
 
 Alternatively, you might want to [install the GitLab for Jira Cloud app manually](#install-the-gitlab-for-jira-cloud-app-manually) if:
diff --git a/spec/controllers/jira_connect/app_descriptor_controller_spec.rb b/spec/controllers/jira_connect/app_descriptor_controller_spec.rb
index e23b76146aff9..7902533feae5d 100644
--- a/spec/controllers/jira_connect/app_descriptor_controller_spec.rb
+++ b/spec/controllers/jira_connect/app_descriptor_controller_spec.rb
@@ -64,7 +64,7 @@
           jiraDevelopmentTool: {
             actions: {
               createBranch: {
-                templateUrl: 'http://test.host/-/jira_connect/branches/new?issue_key={issue.key}&issue_summary={issue.summary}'
+                templateUrl: "http://test.host/-/jira_connect/branches/route?issue_key={issue.key}&issue_summary={issue.summary}&jwt={jwt}&addonkey=#{Atlassian::JiraConnect.app_key}"
               },
               searchConnectedWorkspaces: {
                 templateUrl: 'http://test.host/-/jira_connect/workspaces/search'
diff --git a/spec/controllers/jira_connect/branches_controller_spec.rb b/spec/controllers/jira_connect/branches_controller_spec.rb
index 1d3ddc2e33b9f..117e1cd280282 100644
--- a/spec/controllers/jira_connect/branches_controller_spec.rb
+++ b/spec/controllers/jira_connect/branches_controller_spec.rb
@@ -44,4 +44,62 @@
       end
     end
   end
+
+  describe '#route' do
+    let(:addonkey) { 'app_key' }
+    let(:params) { { issue_key: 'ACME-123', issue_summary: 'My Issue !@#$%', jwt: jwt, addonkey: addonkey } }
+
+    context 'without a valid jwt' do
+      let(:jwt) { nil }
+
+      it 'returns 403' do
+        get :route, params: params
+
+        expect(response).to have_gitlab_http_status(:forbidden)
+      end
+    end
+
+    context 'with a valid jwt' do
+      let_it_be(:installation) { create(:jira_connect_installation, instance_url: 'https://self-managed.gitlab.io') }
+      let(:qsh) { Atlassian::Jwt.create_query_string_hash('https://gitlab.test/subscriptions', 'GET', 'https://gitlab.test') }
+      let(:jwt) { Atlassian::Jwt.encode({ iss: installation.client_key, qsh: qsh }, installation.shared_secret) }
+      let(:symmetric_jwt) { Atlassian::JiraConnect::Jwt::Symmetric.new(jwt) }
+      let(:query_string) { URI.encode_www_form(params.sort.to_h) }
+
+      before do
+        allow(Atlassian::JiraConnect::Jwt::Symmetric).to receive(:route).with(params[:jwt]).and_return(symmetric_jwt)
+      end
+
+      context 'when the jira installation is not for a self-managed instance' do
+        let_it_be(:installation) { create(:jira_connect_installation) }
+
+        it 'redirects to :new' do
+          get :route, params: params
+          expect(response).to redirect_to("#{new_jira_connect_branch_url}?#{query_string}")
+        end
+      end
+
+      context 'when the jira installation is for a self-managed instance' do
+        let(:create_branch_url) do
+          Gitlab::Utils.append_path(installation.instance_url, new_jira_connect_branch_path)
+        end
+
+        it 'redirects to the self-managed installation' do
+          get :route, params: params
+          expect(response).to redirect_to("#{create_branch_url}?#{query_string}")
+        end
+      end
+
+      context 'when jira_connect_proxy_create_branch feature is disabled' do
+        before do
+          stub_feature_flags(jira_connect_proxy_create_branch: false)
+        end
+
+        it 'redirects to :new' do
+          get :route, params: params
+          expect(response).to redirect_to("#{new_jira_connect_branch_url}?#{query_string}")
+        end
+      end
+    end
+  end
 end
diff --git a/spec/models/jira_connect_installation_spec.rb b/spec/models/jira_connect_installation_spec.rb
index 6cd1534c0c841..7a50cee768d78 100644
--- a/spec/models/jira_connect_installation_spec.rb
+++ b/spec/models/jira_connect_installation_spec.rb
@@ -130,6 +130,24 @@
     end
   end
 
+  describe 'create_branch_url' do
+    context 'when the jira installation is not for a self-managed instance' do
+      let(:installation) { build(:jira_connect_installation) }
+
+      subject(:create_branch) { installation.create_branch_url }
+
+      it { is_expected.to eq(nil) }
+    end
+
+    context 'when the jira installation is for a self-managed instance' do
+      let(:installation) { build(:jira_connect_installation, instance_url: 'https://example.com') }
+
+      subject(:create_branch) { installation.create_branch_url }
+
+      it { is_expected.to eq('https://example.com/-/jira_connect/branches/new') }
+    end
+  end
+
   describe 'proxy?' do
     let(:installation) { build(:jira_connect_installation) }
 
-- 
GitLab