diff --git a/app/services/web_hooks/create_service.rb b/app/services/web_hooks/create_service.rb index fe2d28ae145ae3f8f23bcacfc485dc92f632b993..c187590fd61c64a8550cfec3c1679fb0562ca38b 100644 --- a/app/services/web_hooks/create_service.rb +++ b/app/services/web_hooks/create_service.rb @@ -12,7 +12,7 @@ def execute(hook_params, relation) hook = relation.new(hook_params) if hook.save - success({ hook: hook, async: false }) + after_create(hook) else return error("Invalid url given", 422) if hook.errors[:url].present? return error("Invalid branch filter given", 422) if hook.errors[:push_events_branch_filter].present? @@ -23,6 +23,12 @@ def execute(hook_params, relation) private + def after_create(hook) + success({ hook: hook, async: false }) + end + attr_reader :current_user end end + +WebHooks::CreateService.prepend_mod_with('WebHooks::CreateService') diff --git a/config/audit_events/types/webhook_created.yml b/config/audit_events/types/webhook_created.yml new file mode 100644 index 0000000000000000000000000000000000000000..17612416a318a1b568f03c5ccaac73aa5cd3057c --- /dev/null +++ b/config/audit_events/types/webhook_created.yml @@ -0,0 +1,10 @@ +--- +name: webhook_created +description: Event triggered when a webhook is created. +introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/8068 +introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/154046 +feature_category: webhooks +milestone: '17.1' +saved_to_database: true +streamed: true +scope: [Project, Group, Instance] \ No newline at end of file diff --git a/doc/user/compliance/audit_event_types.md b/doc/user/compliance/audit_event_types.md index b5483ceaf877c18797d151c492c7851327fbc13c..d42185b7c18bd0fef88324a231d2ac53b21c96d8 100644 --- a/doc/user/compliance/audit_event_types.md +++ b/doc/user/compliance/audit_event_types.md @@ -542,4 +542,5 @@ Audit event types belong to the following product categories. | Name | Description | Saved to database | Streamed | Introduced in | Scope | |:------------|:------------|:------------------|:---------|:--------------|:--------------| +| [`webhook_created`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/154046) | Event triggered when a webhook is created.| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [17.1](https://gitlab.com/gitlab-org/gitlab/-/issues/8068) | Project, Group, Instance | | [`webhook_destroyed`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102342) | Event triggered when a webhook is destroyed.| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [17.0](https://gitlab.com/gitlab-org/gitlab/-/issues/458817) | Project, Group, Instance | diff --git a/ee/app/services/ee/web_hooks/create_service.rb b/ee/app/services/ee/web_hooks/create_service.rb new file mode 100644 index 0000000000000000000000000000000000000000..1799373bc0a3719cc203acfa4cf417ba1a5a380f --- /dev/null +++ b/ee/app/services/ee/web_hooks/create_service.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module EE + module WebHooks + module CreateService + extend ::Gitlab::Utils::Override + + private + + override :after_create + def after_create(hook) + result = super + log_audit_event(hook) + result + end + + def log_audit_event(hook) + audit_context = { + name: "webhook_created", + author: current_user, + scope: hook.parent || current_user, + target: hook, + message: "Created #{hook.model_name.human.downcase}", + target_details: "Hook #{hook.id}" + } + + ::Gitlab::Audit::Auditor.audit(audit_context) + end + end + end +end diff --git a/ee/spec/services/ee/web_hooks/create_service_spec.rb b/ee/spec/services/ee/web_hooks/create_service_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..b1505f0c0b754443d195343c30095d42879c9a4f --- /dev/null +++ b/ee/spec/services/ee/web_hooks/create_service_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WebHooks::CreateService, :sidekiq_inline, feature_category: :webhooks do + let_it_be(:current_user) { create(:user) } + + describe '#execute' do + # Testing with a project hook only - for permission tests, see policy specs. + let_it_be(:project) { create(:project) } + let_it_be(:relation) { ProjectHook.none } + let(:hook_params) { { url: 'https://example.com/hook', project_id: project.id } } + + subject(:webhook_created) { described_class.new(current_user) } + + context 'when creating a project hook succeeds' do + it 'creates an audit event', :aggregate_failures do + webhook_created.execute(hook_params, relation) + + expect(AuditEvent.last).to have_attributes( + author_name: current_user.name, + author_id: current_user.id, + target_type: "ProjectHook", + target_details: "Hook #{ProjectHook.last.id}", + details: include(custom_message: "Created project hook") + ) + end + end + + context 'when creating a project hook fails' do + it 'does not create an audit event' do + hook_params[:url] = 'invalid_url' + + response = webhook_created.execute(hook_params, relation) + + expect { response }.not_to change { AuditEvent.count } + end + end + end +end