Commit b8f02ab0 authored by Andy Soiron's avatar Andy Soiron Committed by Alex Kalderimis

Forward Jira uninstall events to self-managed

When instance_url is set the JiraConnectInstallation
serves as proxy for a self-managed instance. In this
case it should forward the event.
parent 526e99ae
......@@ -19,7 +19,7 @@ class JiraConnect::EventsController < JiraConnect::ApplicationController
end
def uninstalled
if current_jira_installation.destroy
if JiraConnectInstallations::DestroyService.execute(current_jira_installation, jira_connect_base_path, jira_connect_events_uninstalled_path)
head :ok
else
head :unprocessable_entity
......
# frozen_string_literal: true
module JiraConnectInstallations
class DestroyService
def self.execute(installation, jira_connect_base_path, jira_connect_uninstalled_event_path)
new(installation, jira_connect_base_path, jira_connect_uninstalled_event_path).execute
end
def initialize(installation, jira_connect_base_path, jira_connect_uninstalled_event_path)
@installation = installation
@jira_connect_base_path = jira_connect_base_path
@jira_connect_uninstalled_event_path = jira_connect_uninstalled_event_path
end
def execute
if @installation.instance_url.present?
JiraConnect::ForwardEventWorker.perform_async(@installation.id, @jira_connect_base_path, @jira_connect_uninstalled_event_path)
return true
end
@installation.destroy
end
end
end
......@@ -1078,6 +1078,15 @@
:weight: 2
:idempotent: true
:tags: []
- :name: jira_connect:jira_connect_forward_event
:worker_name: JiraConnect::ForwardEventWorker
:feature_category: :integrations
:has_external_dependencies: true
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:tags: []
- :name: jira_connect:jira_connect_sync_branch
:worker_name: JiraConnect::SyncBranchWorker
:feature_category: :integrations
......
# frozen_string_literal: true
module JiraConnect
class ForwardEventWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
queue_namespace :jira_connect
feature_category :integrations
worker_has_external_dependencies!
def perform(installation_id, base_path, event_path)
installation = JiraConnectInstallation.find_by_id(installation_id)
return if installation&.instance_url.nil?
proxy_url = installation.instance_url + event_path
qsh = Atlassian::Jwt.create_query_string_hash(proxy_url, 'POST', installation.instance_url + base_path)
jwt = Atlassian::Jwt.encode({ iss: installation.client_key, qsh: qsh }, installation.shared_secret)
Gitlab::HTTP.post(proxy_url, headers: { 'Authorization' => "JWT #{jwt}" })
ensure
installation.destroy if installation
end
end
end
......@@ -66,19 +66,19 @@ RSpec.describe JiraConnect::EventsController do
request.headers['Authorization'] = "JWT #{auth_token}"
end
subject { post :uninstalled }
subject(:post_uninstalled) { post :uninstalled }
context 'when JWT is invalid' do
let(:auth_token) { 'invalid_token' }
it 'returns 403' do
subject
post_uninstalled
expect(response).to have_gitlab_http_status(:forbidden)
end
it 'does not delete the installation' do
expect { subject }.not_to change { JiraConnectInstallation.count }
expect { post_uninstalled }.not_to change { JiraConnectInstallation.count }
end
end
......@@ -87,8 +87,27 @@ RSpec.describe JiraConnect::EventsController do
Atlassian::Jwt.encode({ iss: installation.client_key, qsh: qsh }, installation.shared_secret)
end
it 'deletes the installation' do
expect { subject }.to change { JiraConnectInstallation.count }.by(-1)
let(:jira_base_path) { '/-/jira_connect' }
let(:jira_event_path) { '/-/jira_connect/events/uninstalled' }
it 'calls the DestroyService and returns ok in case of success' do
expect_next_instance_of(JiraConnectInstallations::DestroyService, installation, jira_base_path, jira_event_path) do |destroy_service|
expect(destroy_service).to receive(:execute).and_return(true)
end
post_uninstalled
expect(response).to have_gitlab_http_status(:ok)
end
it 'calls the DestroyService and returns unprocessable_entity in case of failure' do
expect_next_instance_of(JiraConnectInstallations::DestroyService, installation, jira_base_path, jira_event_path) do |destroy_service|
expect(destroy_service).to receive(:execute).and_return(false)
end
post_uninstalled
expect(response).to have_gitlab_http_status(:unprocessable_entity)
end
end
end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe JiraConnectInstallations::DestroyService do
describe '.execute' do
it 'creates an instance and calls execute' do
expect_next_instance_of(described_class, 'param1', 'param2', 'param3') do |destroy_service|
expect(destroy_service).to receive(:execute)
end
described_class.execute('param1', 'param2', 'param3')
end
end
describe '#execute' do
let!(:installation) { create(:jira_connect_installation) }
let(:jira_base_path) { '/-/jira_connect' }
let(:jira_event_path) { '/-/jira_connect/events/uninstalled' }
subject { described_class.new(installation, jira_base_path, jira_event_path).execute }
it { is_expected.to be_truthy }
it 'deletes the installation' do
expect { subject }.to change(JiraConnectInstallation, :count).by(-1)
end
context 'and the installation has an instance_url set' do
let!(:installation) { create(:jira_connect_installation, instance_url: 'http://example.com') }
it { is_expected.to be_truthy }
it 'schedules a ForwardEventWorker background job and keeps the installation' do
expect(JiraConnect::ForwardEventWorker).to receive(:perform_async).with(installation.id, jira_base_path, jira_event_path)
expect { subject }.not_to change(JiraConnectInstallation, :count)
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe JiraConnect::ForwardEventWorker do
describe '#perform' do
let!(:jira_connect_installation) { create(:jira_connect_installation, instance_url: self_managed_url, client_key: client_key, shared_secret: shared_secret) }
let(:base_path) { '/-/jira_connect' }
let(:event_path) { '/-/jira_connect/events/uninstalled' }
let(:self_managed_url) { 'http://example.com' }
let(:base_url) { self_managed_url + base_path }
let(:event_url) { self_managed_url + event_path }
let(:client_key) { '123' }
let(:shared_secret) { '123' }
subject { described_class.new.perform(jira_connect_installation.id, base_path, event_path) }
it 'forwards the event including the auth header and deletes the installation' do
stub_request(:post, event_url)
expect(Atlassian::Jwt).to receive(:create_query_string_hash).with(event_url, 'POST', base_url).and_return('some_qsh')
expect(Atlassian::Jwt).to receive(:encode).with({ iss: client_key, qsh: 'some_qsh' }, shared_secret).and_return('auth_token')
expect { subject }.to change(JiraConnectInstallation, :count).by(-1)
expect(WebMock).to have_requested(:post, event_url).with(headers: { 'Authorization' => 'JWT auth_token' })
end
context 'when installation does not exist' do
let(:jira_connect_installation) { instance_double(JiraConnectInstallation, id: -1) }
it 'does nothing' do
expect { subject }.not_to change(JiraConnectInstallation, :count)
end
end
context 'when installation does not have an instance_url' do
let!(:jira_connect_installation) { create(:jira_connect_installation) }
it 'forwards the event including the auth header' do
expect { subject }.to change(JiraConnectInstallation, :count).by(-1)
expect(WebMock).not_to have_requested(:post, '*')
end
end
context 'when it fails to forward the event' do
it 'still deletes the installation' do
allow(Gitlab::HTTP).to receive(:post).and_raise(StandardError)
expect { subject }.to raise_error(StandardError).and change(JiraConnectInstallation, :count).by(-1)
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment