diff --git a/app/services/projects/unlink_fork_service.rb b/app/services/projects/unlink_fork_service.rb index 9eccc16a8b25d947321d620b371fe6c45f05ae26..898421364db3a17d5e22f3a5da2192cf219ded94 100644 --- a/app/services/projects/unlink_fork_service.rb +++ b/app/services/projects/unlink_fork_service.rb @@ -60,3 +60,5 @@ def refresh_forks_count(project) end end end + +Projects::UnlinkForkService.prepend_mod_with('Projects::UnlinkForkService') diff --git a/ee/app/services/ee/projects/unlink_fork_service.rb b/ee/app/services/ee/projects/unlink_fork_service.rb new file mode 100644 index 0000000000000000000000000000000000000000..0aedf1e8b72b50e6bc6ede418d2d6a990a4a6ea4 --- /dev/null +++ b/ee/app/services/ee/projects/unlink_fork_service.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module EE + module Projects + module UnlinkForkService + extend ::Gitlab::Utils::Override + + override :execute + def execute + super.tap do |result| + log_audit_event if result.present? + end + end + + def log_audit_event + audit_context = { + name: 'project_fork_relationship_removed', + author: current_user, + scope: project, + target: project, + message: "Project unlinked from #{project.forked_from_project&.name}", + created_at: DateTime.current + } + + ::Gitlab::Audit::Auditor.audit(audit_context) + end + end + end +end diff --git a/ee/config/audit_events/types/project_fork_relationship_removed.yml b/ee/config/audit_events/types/project_fork_relationship_removed.yml new file mode 100644 index 0000000000000000000000000000000000000000..9a81ff95c962091e7f862c0faef0816ceed66bc3 --- /dev/null +++ b/ee/config/audit_events/types/project_fork_relationship_removed.yml @@ -0,0 +1,8 @@ +name: project_fork_relationship_removed +description: Event triggered on successful removal of project's fork relationship +introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/272532 +introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/101017 +group: "group::compliance" +milestone: "15.6" +saved_to_database: true +streamed: true diff --git a/ee/spec/services/ee/projects/unlink_fork_service_spec.rb b/ee/spec/services/ee/projects/unlink_fork_service_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..7509c5306241610412de466a59ea8f433c383a74 --- /dev/null +++ b/ee/spec/services/ee/projects/unlink_fork_service_spec.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Projects::UnlinkForkService, :use_clean_rails_memory_store_caching do + include ProjectForksHelper + + subject(:unlink_fork) { described_class.new(forked_project, user).execute } + + let_it_be(:project) { create(:project, :public) } + let_it_be(:user) { create(:user) } + + context 'when forked project is unlinked from parent' do + let!(:forked_project) { fork_project(project, user) } + + it 'creates an audit event', :aggregate_failures do + expect(::Gitlab::Audit::Auditor) + .to receive(:audit).with( + hash_including({ name: "project_fork_relationship_removed" })).and_call_original + + expect { unlink_fork }.to change(AuditEvent, :count).by(1) + + expect(AuditEvent.last).to have_attributes({ + author: user, + entity_id: forked_project.id, + target_type: project.class.name, + details: { + author_class: user.class.name, + author_name: user.name, + custom_message: "Project unlinked from #{project.name}", + target_details: forked_project.name, + target_id: forked_project.id, + target_type: forked_project.class.name + } + }) + end + + context 'when forked project does not exist' do + before do + project.destroy! + end + + it 'creates an audit event', :aggregate_failures do + expect(::Gitlab::Audit::Auditor) + .to receive(:audit).with( + hash_including({ name: "project_fork_relationship_removed" })).and_call_original + + expect { unlink_fork }.to change(AuditEvent, :count).by(1) + + expect(AuditEvent.last).to have_attributes({ + author: user, + entity_id: forked_project.id, + target_type: project.class.name, + details: { + author_class: user.class.name, + author_name: user.name, + custom_message: "Project unlinked from ", + target_details: forked_project.name, + target_id: forked_project.id, + target_type: forked_project.class.name + } + }) + end + end + end + + context 'when no unlinking is performed' do + let(:forked_project) { project } + + it 'does not create an audit event' do + expect { subject }.not_to change(AuditEvent, :count) + end + end +end