diff --git a/app/models/concerns/integrations/base/integration.rb b/app/models/concerns/integrations/base/integration.rb index 40c823b529d51a07a466d30667ea75346a52e5b2..4a2256d05dedf34907e42e7ae15a7d8090836463 100644 --- a/app/models/concerns/integrations/base/integration.rb +++ b/app/models/concerns/integrations/base/integration.rb @@ -308,6 +308,9 @@ def build_from_integration(integration, project_id: nil, group_id: nil) new_integration.project_id = project_id new_integration.group_id = group_id new_integration.inherit_from_id = integration.id if integration.inheritable? + + integration.after_build_from_integration(new_integration) + new_integration end @@ -576,6 +579,12 @@ def fields self.class.fields.dup end + # Hook for integrations to configure associations after duplication. + # Override in subclasses when associations need the parent context. + def after_build_from_integration(new_integration) + # no-op + end + # Duplicating an integration also duplicates the data fields. Duped records have different ciphertexts. override :dup def dup diff --git a/app/models/integrations/gitlab_slack_application.rb b/app/models/integrations/gitlab_slack_application.rb index 2ed5a38333a45889992e200c4da6261467370393..de411ddbe680d6f62e28e56b35e18e4b62b467ab 100644 --- a/app/models/integrations/gitlab_slack_application.rb +++ b/app/models/integrations/gitlab_slack_application.rb @@ -79,6 +79,15 @@ def configurable_events super end + def after_build_from_integration(new_integration) + return unless slack_integration + + new_integration.slack_integration = slack_integration.dup.tap do |entity| + entity.alias = new_integration.parent&.full_path || SlackIntegration::INSTANCE_ALIAS + entity.authorized_scope_names = slack_integration.authorized_scope_names + end + end + override :requires_webhook? def self.requires_webhook? false diff --git a/app/models/slack_integration.rb b/app/models/slack_integration.rb index 7900203b993ddaac646619cbe87bc0e25860a409..aae5721388d4868a057f4b6fcc533055976fa6db 100644 --- a/app/models/slack_integration.rb +++ b/app/models/slack_integration.rb @@ -9,6 +9,7 @@ class SlackIntegration < ApplicationRecord SCOPE_COMMANDS = 'commands' SCOPE_CHAT_WRITE = 'chat:write' SCOPE_CHAT_WRITE_PUBLIC = 'chat:write.public' + INSTANCE_ALIAS = '_gitlab-instance' # These scopes are requested when installing the app, additional scopes # will need reauthorization. diff --git a/app/services/integrations/slack_installation/instance_service.rb b/app/services/integrations/slack_installation/instance_service.rb index 2d53547cd3cd372c0f6176403493f55fef145b84..8128fc58a081004a030c9ead3211d4d3f38eeb83 100644 --- a/app/services/integrations/slack_installation/instance_service.rb +++ b/app/services/integrations/slack_installation/instance_service.rb @@ -10,7 +10,7 @@ def redirect_uri end def installation_alias - '_gitlab-instance' + SlackIntegration::INSTANCE_ALIAS end def authorized? diff --git a/spec/models/integration_spec.rb b/spec/models/integration_spec.rb index b29f65e5a9ecad8e5f93ad3a60ecc799efcff205..e239fa025d0e87dab751d214c8874625eadf3a70 100644 --- a/spec/models/integration_spec.rb +++ b/spec/models/integration_spec.rb @@ -480,6 +480,14 @@ def integration_hash(type) end describe '.build_from_integration' do + it 'calls after_build_from_integration hook' do + integration = create(:integration, :instance) + + expect(integration).to receive(:after_build_from_integration).with(kind_of(described_class)) + + described_class.build_from_integration(integration, project_id: project.id) + end + context 'when integration is an instance-level integration' do let(:instance_integration) { create(:jira_integration, :instance) } diff --git a/spec/models/integrations/gitlab_slack_application_spec.rb b/spec/models/integrations/gitlab_slack_application_spec.rb index 8d6366f61f6648b24729beb8aa9caecd70b8bfe7..fb3247a1f72467f3c500bdebc33fd9864840cb8f 100644 --- a/spec/models/integrations/gitlab_slack_application_spec.rb +++ b/spec/models/integrations/gitlab_slack_application_spec.rb @@ -334,4 +334,44 @@ def stub_slack_request(channel:, success:) end end end + + describe '#after_build_from_integration' do + let(:slack_integration) { create(:slack_integration) } + let(:initial_integration) do + create(:gitlab_slack_application_integration, :instance, slack_integration: slack_integration) + end + + let(:project) { create(:project) } + let(:new_integration) { build(:gitlab_slack_application_integration, project: project) } + + it 'duplicates slack_integration with correct alias' do + initial_integration.after_build_from_integration(new_integration) + + new_slack_integration = new_integration.slack_integration + expect(new_slack_integration.slack_api_scopes.ids).to match_array(slack_integration.slack_api_scopes.ids) + expect(new_slack_integration).to have_attributes( + alias: project.full_path, + team_id: slack_integration.team_id, + team_name: slack_integration.team_name, + user_id: slack_integration.user_id, + bot_user_id: slack_integration.bot_user_id, + bot_access_token: slack_integration.bot_access_token + ) + end + + context 'when parent integration has no slack_integration' do + let(:initial_integration) do + create(:gitlab_slack_application_integration, slack_integration: nil, active: false) + end + + it 'does not duplicate slack_integration and does not activate the new integration on save' do + new_integration = described_class.build_from_integration(initial_integration, project_id: project.id) + + expect(new_integration.slack_integration).to be_nil + expect(new_integration).not_to be_active + expect { new_integration.save! }.not_to change { SlackIntegration.count } + expect(new_integration).not_to be_active + end + end + end end diff --git a/spec/models/slack_integration_spec.rb b/spec/models/slack_integration_spec.rb index 4766f07270ad6e965a90158cf87d5081b76adc60..f96b4188b07ae2cb715603fc68950336d4c8abe3 100644 --- a/spec/models/slack_integration_spec.rb +++ b/spec/models/slack_integration_spec.rb @@ -9,6 +9,12 @@ it { is_expected.to belong_to(:integration) } end + describe 'constant' do + it 'is expected that DEFAULT_ALIAS is defined with the correct value' do + expect(SlackIntegration::INSTANCE_ALIAS).to eq('_gitlab-instance') + end + end + describe 'authorized_scope_names' do subject(:slack_integration) { integration }