diff --git a/app/services/merge_requests/close_service.rb b/app/services/merge_requests/close_service.rb index 867e8221e5e01fd0a6af4b61e30d485f874562b1..62928e05a8995b018493c07eedcc444c7501ea90 100644 --- a/app/services/merge_requests/close_service.rb +++ b/app/services/merge_requests/close_service.rb @@ -51,4 +51,4 @@ def trigger_merge_request_merge_status_updated(merge_request) end end -MergeRequests::CloseService.prepend_mod_with('MergeRequests::CloseService') +MergeRequests::CloseService.prepend_mod diff --git a/app/services/merge_requests/reopen_service.rb b/app/services/merge_requests/reopen_service.rb index d2247a6d4c110b35fd7744f3c846f6359b633a55..b2e15cc7c7e442a46971db7d012975f654d80d09 100644 --- a/app/services/merge_requests/reopen_service.rb +++ b/app/services/merge_requests/reopen_service.rb @@ -37,3 +37,5 @@ def create_event(merge_request) end end end + +MergeRequests::ReopenService.prepend_mod diff --git a/ee/app/services/ee/merge_requests/after_create_service.rb b/ee/app/services/ee/merge_requests/after_create_service.rb index feaf0b76b2f48daf44a90030522fa5a462c56034..1ff370798aa0826a5a313d72344456089cce01be 100644 --- a/ee/app/services/ee/merge_requests/after_create_service.rb +++ b/ee/app/services/ee/merge_requests/after_create_service.rb @@ -9,6 +9,11 @@ module AfterCreateService def prepare_merge_request(merge_request) super + if current_user.project_bot? + log_audit_event(merge_request, 'merge_request_created_by_project_bot', + "Created merge request #{merge_request.title}") + end + schedule_sync_for(merge_request) schedule_fetch_suggested_reviewers(merge_request) end diff --git a/ee/app/services/ee/merge_requests/base_service.rb b/ee/app/services/ee/merge_requests/base_service.rb index 40cc5231f28fd34c4445dbac742997fc3c10f1ad..e14892443e2bd734344f59cf78bb7f2fa3f9aa25 100644 --- a/ee/app/services/ee/merge_requests/base_service.rb +++ b/ee/app/services/ee/merge_requests/base_service.rb @@ -108,6 +108,27 @@ def capture_suggested_reviewers_accepted(merge_request) ::MergeRequests::CaptureSuggestedReviewersAcceptedWorker .perform_async(merge_request.id, suggested_reviewer_ids) end + + def log_audit_event(merge_request, event_name, message) + audit_context = { + name: event_name, + author: current_user, + scope: merge_request.target_project, + target: merge_request, + message: message, + target_details: + { iid: merge_request.iid, + id: merge_request.id, + source_branch: merge_request.source_branch, + target_branch: merge_request.target_branch } + } + + if event_name == 'merge_request_merged_by_project_bot' + audit_context[:target_details][:merge_commit_sha] = merge_request.merge_commit_sha + end + + ::Gitlab::Audit::Auditor.audit(audit_context) + end end end end diff --git a/ee/app/services/ee/merge_requests/close_service.rb b/ee/app/services/ee/merge_requests/close_service.rb index 5721df914e5ea09bb580c78f1affb90e2952a8b5..92c681f351e156700b827e6f46312e90ac283fbe 100644 --- a/ee/app/services/ee/merge_requests/close_service.rb +++ b/ee/app/services/ee/merge_requests/close_service.rb @@ -3,6 +3,18 @@ module EE module MergeRequests module CloseService + extend ::Gitlab::Utils::Override + + override :execute + def execute(merge_request, commit = nil) + super.tap do + if current_user.project_bot? + log_audit_event(merge_request, 'merge_request_closed_by_project_bot', + "Closed merge request #{merge_request.title}") + end + end + end + def expire_unapproved_key(merge_request) merge_request.approval_state.expire_unapproved_key! end diff --git a/ee/app/services/ee/merge_requests/post_merge_service.rb b/ee/app/services/ee/merge_requests/post_merge_service.rb index 6fac08150593365c6c33a1ef208a212a5864ab19..593827d15e2590078ca1776319aa793b21ad8d55 100644 --- a/ee/app/services/ee/merge_requests/post_merge_service.rb +++ b/ee/app/services/ee/merge_requests/post_merge_service.rb @@ -18,6 +18,12 @@ def execute(merge_request) merge_request.approval_state.expire_unapproved_key! audit_approval_rules(merge_request) + + if current_user.project_bot? + log_audit_event(merge_request, 'merge_request_merged_by_project_bot', + "Merged merge request #{merge_request.title}") + end + sync_security_scan_orchestration_policies(target_project) trigger_blocked_merge_requests_merge_status_updated(merge_request) end diff --git a/ee/app/services/ee/merge_requests/reopen_service.rb b/ee/app/services/ee/merge_requests/reopen_service.rb new file mode 100644 index 0000000000000000000000000000000000000000..58e624714292417750516885e756f74947f1892e --- /dev/null +++ b/ee/app/services/ee/merge_requests/reopen_service.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module EE + module MergeRequests + module ReopenService + extend ::Gitlab::Utils::Override + + override :execute + def execute(merge_request) + super.tap do + if current_user.project_bot? + log_audit_event(merge_request, 'merge_request_reopened_by_project_bot', + "Reopened merge request #{merge_request.title}") + end + end + end + end + end +end diff --git a/ee/app/services/ee/notes/post_process_service.rb b/ee/app/services/ee/notes/post_process_service.rb index 7fe31806ccdf5df851711a49ba374c14511fc987..3c2a19cf897024c524dc5aa3399cbd8a39fd3865 100644 --- a/ee/app/services/ee/notes/post_process_service.rb +++ b/ee/app/services/ee/notes/post_process_service.rb @@ -11,6 +11,28 @@ def execute super ::Analytics::RefreshCommentsData.for_note(note)&.execute + + log_audit_event if note.author.project_bot? + end + + private + + def log_audit_event + audit_context = { + name: 'comment_by_project_bot', + author: note.author, + scope: note.project, + target: note, + message: "Added comment: #{::Gitlab::UrlBuilder.note_url(note)}", + target_details: { + id: note.id, + noteable_type: note.noteable_type, + noteable_id: note.noteable_id + }, + stream_only: true + } + + ::Gitlab::Audit::Auditor.audit(audit_context) end end end diff --git a/ee/config/audit_events/types/comment_by_project_bot.yml b/ee/config/audit_events/types/comment_by_project_bot.yml new file mode 100644 index 0000000000000000000000000000000000000000..6e915a00b33a7292ed97036e2e23bde2e15b21e2 --- /dev/null +++ b/ee/config/audit_events/types/comment_by_project_bot.yml @@ -0,0 +1,9 @@ +--- +name: comment_by_project_bot +description: Triggered when a comment is added to an issue or an MR using the project access token +introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/323299 +introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120927 +feature_category: team_planning +milestone: '16.1' +saved_to_database: false +streamed: true diff --git a/ee/config/audit_events/types/merge_request_closed_by_project_bot.yml b/ee/config/audit_events/types/merge_request_closed_by_project_bot.yml new file mode 100644 index 0000000000000000000000000000000000000000..95c3f7dd322ff065c4f303de8bd15694a748b377 --- /dev/null +++ b/ee/config/audit_events/types/merge_request_closed_by_project_bot.yml @@ -0,0 +1,9 @@ +--- +name: merge_request_closed_by_project_bot +description: Triggered when a merge request is closed using a project access token +introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/323299 +introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120927 +feature_category: code_review_workflow +milestone: '16.1' +saved_to_database: true +streamed: true diff --git a/ee/config/audit_events/types/merge_request_created_by_project_bot.yml b/ee/config/audit_events/types/merge_request_created_by_project_bot.yml new file mode 100644 index 0000000000000000000000000000000000000000..5c88538e9787a275568084c8d2c7eafeb5c46cc9 --- /dev/null +++ b/ee/config/audit_events/types/merge_request_created_by_project_bot.yml @@ -0,0 +1,9 @@ +--- +name: merge_request_created_by_project_bot +description: Triggered when a merge request is created using a project access token +introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/323299 +introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120927 +feature_category: code_review_workflow +milestone: '16.1' +saved_to_database: true +streamed: true diff --git a/ee/config/audit_events/types/merge_request_merged_by_project_bot.yml b/ee/config/audit_events/types/merge_request_merged_by_project_bot.yml new file mode 100644 index 0000000000000000000000000000000000000000..2b2b0f9820edaaba8c829c9664a36bd51a52e2cb --- /dev/null +++ b/ee/config/audit_events/types/merge_request_merged_by_project_bot.yml @@ -0,0 +1,9 @@ +--- +name: merge_request_merged_by_project_bot +description: Triggered when a merge request is merged using a project access token +introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/323299 +introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120927 +feature_category: code_review_workflow +milestone: '16.1' +saved_to_database: true +streamed: true diff --git a/ee/config/audit_events/types/merge_request_reopened_by_project_bot.yml b/ee/config/audit_events/types/merge_request_reopened_by_project_bot.yml new file mode 100644 index 0000000000000000000000000000000000000000..bedbcc41dcdf2299273c7ab12c5c28ed676030ef --- /dev/null +++ b/ee/config/audit_events/types/merge_request_reopened_by_project_bot.yml @@ -0,0 +1,9 @@ +--- +name: merge_request_reopened_by_project_bot +description: Triggered when a merge request is reopened using a project access token +introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/323299 +introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120927 +feature_category: code_review_workflow +milestone: '16.1' +saved_to_database: true +streamed: true diff --git a/ee/spec/services/ee/merge_requests/after_create_service_spec.rb b/ee/spec/services/ee/merge_requests/after_create_service_spec.rb index 85f5f7b54efb49a186c91f61974010c5801acd5c..3760829c07eb5b2e7ca7df6dc6b62a036278980a 100644 --- a/ee/spec/services/ee/merge_requests/after_create_service_spec.rb +++ b/ee/spec/services/ee/merge_requests/after_create_service_spec.rb @@ -112,5 +112,36 @@ end end end + + context 'for audit events' do + let_it_be(:project_bot) { create(:user, :project_bot, email: "bot@example.com") } + let_it_be(:merge_request) { create(:merge_request, author: project_bot) } + + include_examples 'audit event logging' do + let(:operation) { execute } + let(:event_type) { 'merge_request_created_by_project_bot' } + let(:fail_condition!) { expect(project_bot).to receive(:project_bot?).and_return(false) } + let(:attributes) do + { + author_id: project_bot.id, + entity_id: merge_request.target_project.id, + entity_type: 'Project', + details: { + author_name: project_bot.name, + target_id: merge_request.id, + target_type: 'MergeRequest', + target_details: { + iid: merge_request.iid, + id: merge_request.id, + source_branch: merge_request.source_branch, + target_branch: merge_request.target_branch + }.to_s, + author_class: project_bot.class.name, + custom_message: "Created merge request #{merge_request.title}" + } + } + end + end + end end end diff --git a/ee/spec/services/ee/merge_requests/close_service_spec.rb b/ee/spec/services/ee/merge_requests/close_service_spec.rb index 485e43040cec9be2c7f7e2bd5ccd04443ce1e386..44423fe01528d95c3b4a778dcc7cbb307a5c6ec0 100644 --- a/ee/spec/services/ee/merge_requests/close_service_spec.rb +++ b/ee/spec/services/ee/merge_requests/close_service_spec.rb @@ -48,5 +48,36 @@ end end end + + context 'for audit events' do + let_it_be(:project_bot) { create(:user, :project_bot, email: "bot@example.com") } + let_it_be(:merge_request) { create(:merge_request, author: project_bot) } + + include_examples 'audit event logging' do + let(:operation) { service.execute(merge_request) } + let(:event_type) { 'merge_request_closed_by_project_bot' } + let(:fail_condition!) { expect(project_bot).to receive(:project_bot?).and_return(false) } + let(:attributes) do + { + author_id: project_bot.id, + entity_id: merge_request.target_project.id, + entity_type: 'Project', + details: { + author_name: project_bot.name, + target_id: merge_request.id, + target_type: 'MergeRequest', + target_details: { + iid: merge_request.iid, + id: merge_request.id, + source_branch: merge_request.source_branch, + target_branch: merge_request.target_branch + }.to_s, + author_class: project_bot.class.name, + custom_message: "Closed merge request #{merge_request.title}" + } + } + end + end + end end end diff --git a/ee/spec/services/ee/merge_requests/post_merge_service_spec.rb b/ee/spec/services/ee/merge_requests/post_merge_service_spec.rb index cd09ac1526af56a6a6b76ec7a94d1ddde0cd6095..290e721e687846ab94c5d2312dc84b9d72bdef1e 100644 --- a/ee/spec/services/ee/merge_requests/post_merge_service_spec.rb +++ b/ee/spec/services/ee/merge_requests/post_merge_service_spec.rb @@ -218,5 +218,37 @@ expect(merge_request.approval_state.temporarily_unapproved?).to be_falsey end end + + context 'for audit events' do + let_it_be(:project_bot) { create(:user, :project_bot, email: "bot@example.com") } + let_it_be(:merge_request) { create(:merge_request, author: project_bot) } + + include_examples 'audit event logging' do + let(:operation) { subject } + let(:event_type) { 'merge_request_merged_by_project_bot' } + let(:fail_condition!) { expect(project_bot).to receive(:project_bot?).and_return(false) } + let(:attributes) do + { + author_id: project_bot.id, + entity_id: merge_request.target_project.id, + entity_type: 'Project', + details: { + author_name: project_bot.name, + target_id: merge_request.id, + target_type: 'MergeRequest', + target_details: { + iid: merge_request.iid, + id: merge_request.id, + source_branch: merge_request.source_branch, + target_branch: merge_request.target_branch, + merge_commit_sha: merge_request.merge_commit_sha + }.to_s, + author_class: project_bot.class.name, + custom_message: "Merged merge request #{merge_request.title}" + } + } + end + end + end end end diff --git a/ee/spec/services/ee/merge_requests/reopen_service_spec.rb b/ee/spec/services/ee/merge_requests/reopen_service_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..4e25ee5a2969310f59b92793aedb7734425b4fe2 --- /dev/null +++ b/ee/spec/services/ee/merge_requests/reopen_service_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe MergeRequests::ReopenService, feature_category: :code_review_workflow do + describe '#execute' do + let_it_be(:merge_request) { create(:merge_request) } + let_it_be(:project) { merge_request.target_project } + + let(:service_object) { described_class.new(project: project, current_user: merge_request.author) } + + subject(:merge_request_reopen_service) { service_object.execute(merge_request) } + + context 'for audit events' do + let_it_be(:project_bot) { create(:user, :project_bot, email: "bot@example.com") } + let_it_be(:merge_request) { create(:merge_request, author: project_bot) } + + include_examples 'audit event logging' do + let(:operation) { merge_request_reopen_service } + let(:event_type) { 'merge_request_reopened_by_project_bot' } + let(:fail_condition!) { expect(project_bot).to receive(:project_bot?).and_return(false) } + let(:attributes) do + { + author_id: project_bot.id, + entity_id: merge_request.target_project.id, + entity_type: 'Project', + details: { + author_name: project_bot.name, + target_id: merge_request.id, + target_type: 'MergeRequest', + target_details: { + iid: merge_request.iid, + id: merge_request.id, + source_branch: merge_request.source_branch, + target_branch: merge_request.target_branch + }.to_s, + author_class: project_bot.class.name, + custom_message: "Reopened merge request #{merge_request.title}" + } + } + end + end + end + end +end diff --git a/ee/spec/services/ee/notes/post_process_service_spec.rb b/ee/spec/services/ee/notes/post_process_service_spec.rb index e775ac3f7fa3589db3c4235e496a25d07ca3c606..c791f69c1ff45dc5a31c99eeab8eb2b16354e0dc 100644 --- a/ee/spec/services/ee/notes/post_process_service_spec.rb +++ b/ee/spec/services/ee/notes/post_process_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Notes::PostProcessService do +RSpec.describe Notes::PostProcessService, feature_category: :team_planning do describe '#execute' do context 'analytics' do subject { described_class.new(note) } @@ -18,5 +18,41 @@ subject.execute end end + + context 'for audit events' do + subject(:notes_post_process_service) { described_class.new(note) } + + context 'when note author is a project bot' do + let_it_be(:project_bot) { create(:user, :project_bot, email: "bot@example.com") } + + let(:note) { create(:note, author: project_bot) } + + it 'audits with correct name' do + expect(::Gitlab::Audit::Auditor).to receive(:audit).with( + hash_including(name: "comment_by_project_bot", stream_only: true) + ).and_call_original + + notes_post_process_service.execute + end + + it 'does not persist the audit event to database' do + expect { notes_post_process_service.execute }.not_to change { AuditEvent.count } + end + end + + context 'when note author is not a project bot' do + let(:note) { create(:note) } + + it 'does not invoke Gitlab::Audit::Auditor' do + expect(::Gitlab::Audit::Auditor).not_to receive(:audit) + + notes_post_process_service.execute + end + + it 'does not create an audit event' do + expect { notes_post_process_service.execute }.not_to change { AuditEvent.count } + end + end + end end end