diff --git a/doc/administration/audit_event_streaming/audit_event_types.md b/doc/administration/audit_event_streaming/audit_event_types.md
index 88212045d8e1fa54340bc28df0c0d764502f7f6f..1dbf1e19143275ccf1ac4ee4bcab067285e7c70f 100644
--- a/doc/administration/audit_event_streaming/audit_event_types.md
+++ b/doc/administration/audit_event_streaming/audit_event_types.md
@@ -47,6 +47,7 @@ Audit event types belong to the following product categories.
| [`create_event_streaming_destination`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74632) | Event triggered when an external audit event destination is created| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [14.6](https://gitlab.com/gitlab-org/gitlab/-/issues/344664) |
| [`create_http_namespace_filter`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/136047) | Event triggered when a namespace filter for an external audit event destination for a top-level group is created.| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.6](https://gitlab.com/gitlab-org/gitlab/-/issues/424176) |
| [`create_instance_event_streaming_destination`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/123882) | Event triggered when an instance level external audit event destination is created| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.2](https://gitlab.com/gitlab-org/gitlab/-/issues/404730) |
+| [`delete_http_namespace_filter`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/136302) | Event triggered when a namespace filter for an external audit event destination for a top-level group is deleted.| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.7](https://gitlab.com/gitlab-org/gitlab/-/issues/424177) |
| [`destroy_event_streaming_destination`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74632) | Event triggered when an external audit event destination is deleted| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [14.6](https://gitlab.com/gitlab-org/gitlab/-/issues/344664) |
| [`destroy_instance_event_streaming_destination`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/125846) | Event triggered when an instance level external audit event destination is deleted| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.2](https://gitlab.com/gitlab-org/gitlab/-/issues/404730) |
| [`event_type_filters_created`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/113081) | Event triggered when a new audit events streaming event type filter is created| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [15.10](https://gitlab.com/gitlab-org/gitlab/-/issues/344848) |
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 4a1b536fd4002b876afc614f7cd39a5df0fd2348..670591353e39599e59041fc62ec9ac5d0a7bd7d5 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -1587,6 +1587,24 @@ Input type: `AuditEventsStreamingHTTPNamespaceFiltersAddInput`
| `errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| `namespaceFilter` | [`AuditEventStreamingHTTPNamespaceFilter`](#auditeventstreaminghttpnamespacefilter) | Namespace filter created. |
+### `Mutation.auditEventsStreamingHttpNamespaceFiltersDelete`
+
+Input type: `AuditEventsStreamingHTTPNamespaceFiltersDeleteInput`
+
+#### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| `namespaceFilterId` | [`AuditEventsStreamingHTTPNamespaceFilterID!`](#auditeventsstreaminghttpnamespacefilterid) | Namespace filter ID. |
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| `errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
+
### `Mutation.auditEventsStreamingInstanceHeadersCreate`
Input type: `AuditEventsStreamingInstanceHeadersCreateInput`
@@ -14109,6 +14127,17 @@ Autogenerated return type of AuditEventsStreamingHTTPNamespaceFiltersAdd.
| `errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| `namespaceFilter` | [`AuditEventStreamingHTTPNamespaceFilter`](#auditeventstreaminghttpnamespacefilter) | Namespace filter created. |
+### `AuditEventsStreamingHTTPNamespaceFiltersDeletePayload`
+
+Autogenerated return type of AuditEventsStreamingHTTPNamespaceFiltersDelete.
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| `errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
+
### `AuditEventsStreamingInstanceHeader`
Represents a HTTP header key/value that belongs to an instance level audit streaming destination.
@@ -30724,6 +30753,12 @@ A `AuditEventsInstanceGoogleCloudLoggingConfigurationID` is a global ID. It is e
An example `AuditEventsInstanceGoogleCloudLoggingConfigurationID` is: `"gid://gitlab/AuditEvents::Instance::GoogleCloudLoggingConfiguration/1"`.
+### `AuditEventsStreamingHTTPNamespaceFilterID`
+
+A `AuditEventsStreamingHTTPNamespaceFilterID` is a global ID. It is encoded as a string.
+
+An example `AuditEventsStreamingHTTPNamespaceFilterID` is: `"gid://gitlab/AuditEvents::Streaming::HTTP::NamespaceFilter/1"`.
+
### `AuditEventsStreamingHeaderID`
A `AuditEventsStreamingHeaderID` is a global ID. It is encoded as a string.
diff --git a/ee/app/graphql/ee/types/mutation_type.rb b/ee/app/graphql/ee/types/mutation_type.rb
index 42ffa61607094020e894db7cd827a650c001b06a..b7d5567874ab6f354d0dc6a4b90b02542c72899c 100644
--- a/ee/app/graphql/ee/types/mutation_type.rb
+++ b/ee/app/graphql/ee/types/mutation_type.rb
@@ -143,6 +143,7 @@ module MutationType
mount_mutation ::Mutations::Analytics::CycleAnalytics::ValueStreams::Update, alpha: { milestone: '16.6' }
mount_mutation ::Mutations::Analytics::CycleAnalytics::ValueStreams::Destroy, alpha: { milestone: '16.6' }
mount_mutation ::Mutations::AuditEvents::Streaming::HTTP::NamespaceFilters::Create
+ mount_mutation ::Mutations::AuditEvents::Streaming::HTTP::NamespaceFilters::Delete
prepend(Types::DeprecatedMutations)
end
diff --git a/ee/app/graphql/mutations/audit_events/streaming/http/namespace_filters/base.rb b/ee/app/graphql/mutations/audit_events/streaming/http/namespace_filters/base.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1bf63c9faee97b61a271bb0f7c27a07a5f166b1f
--- /dev/null
+++ b/ee/app/graphql/mutations/audit_events/streaming/http/namespace_filters/base.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Mutations
+ module AuditEvents
+ module Streaming
+ module HTTP
+ module NamespaceFilters
+ class Base < BaseMutation
+ authorize :admin_external_audit_events
+
+ private
+
+ def audit(filter, action:)
+ audit_context = {
+ name: "#{action}_http_namespace_filter",
+ author: current_user,
+ scope: filter.external_audit_event_destination.group,
+ target: filter.external_audit_event_destination,
+ message: "#{action.capitalize} namespace filter for http audit event streaming destination " \
+ "#{filter.external_audit_event_destination.name} and namespace #{filter.namespace.full_path}"
+ }
+
+ ::Gitlab::Audit::Auditor.audit(audit_context)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/ee/app/graphql/mutations/audit_events/streaming/http/namespace_filters/create.rb b/ee/app/graphql/mutations/audit_events/streaming/http/namespace_filters/create.rb
index 054d60d0cb3be11c9685e188fceb35efae17228a..24078f5b32b81d52623542dd1aa892fb3cb7b850 100644
--- a/ee/app/graphql/mutations/audit_events/streaming/http/namespace_filters/create.rb
+++ b/ee/app/graphql/mutations/audit_events/streaming/http/namespace_filters/create.rb
@@ -5,9 +5,8 @@ module AuditEvents
module Streaming
module HTTP
module NamespaceFilters
- class Create < BaseMutation
+ class Create < Base
graphql_name 'AuditEventsStreamingHTTPNamespaceFiltersAdd'
- authorize :admin_external_audit_events
argument :destination_id, ::Types::GlobalIDType[::AuditEvents::ExternalAuditEventDestination],
required: true,
@@ -66,19 +65,6 @@ def namespace(group_path, project_path)
namespace.project_namespace
end
- def audit(filter, action:)
- audit_context = {
- name: "#{action}_http_namespace_filter",
- author: current_user,
- scope: filter.external_audit_event_destination.group,
- target: filter.external_audit_event_destination,
- message: "#{action.capitalize} namespace filter for http audit event streaming destination " \
- "#{filter.external_audit_event_destination.name} and namespace #{filter.namespace.full_path}"
- }
-
- ::Gitlab::Audit::Auditor.audit(audit_context)
- end
-
def mutually_exclusive_args
[:group_path, :project_path]
end
diff --git a/ee/app/graphql/mutations/audit_events/streaming/http/namespace_filters/delete.rb b/ee/app/graphql/mutations/audit_events/streaming/http/namespace_filters/delete.rb
new file mode 100644
index 0000000000000000000000000000000000000000..80fb79f359638d4704e578fbd4d61d7db2e49b15
--- /dev/null
+++ b/ee/app/graphql/mutations/audit_events/streaming/http/namespace_filters/delete.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Mutations
+ module AuditEvents
+ module Streaming
+ module HTTP
+ module NamespaceFilters
+ class Delete < Base
+ graphql_name 'AuditEventsStreamingHTTPNamespaceFiltersDelete'
+
+ argument :namespace_filter_id, ::Types::GlobalIDType[::AuditEvents::Streaming::HTTP::NamespaceFilter],
+ required: true,
+ description: 'Namespace filter ID.'
+ def resolve(namespace_filter_id:)
+ filter = authorized_find!(id: namespace_filter_id)
+
+ audit(filter, action: :delete) if filter.destroy
+
+ { namespace_filter: nil, errors: [] }
+ end
+
+ private
+
+ def find_object(id:)
+ ::GitlabSchema.object_from_id(id, expected_type: ::AuditEvents::Streaming::HTTP::NamespaceFilter)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/ee/config/audit_events/types/delete_http_namespace_filter.yml b/ee/config/audit_events/types/delete_http_namespace_filter.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f4843aa3ad285edc0e64519e80c5cf626438c919
--- /dev/null
+++ b/ee/config/audit_events/types/delete_http_namespace_filter.yml
@@ -0,0 +1,8 @@
+name: delete_http_namespace_filter
+description: Event triggered when a namespace filter for an external audit event destination for a top-level group is deleted.
+introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/424177
+introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/136302
+feature_category: audit_events
+milestone: "16.7"
+saved_to_database: true
+streamed: true
diff --git a/ee/spec/requests/api/graphql/audit_events/streaming/http/namespace_filters/delete_spec.rb b/ee/spec/requests/api/graphql/audit_events/streaming/http/namespace_filters/delete_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..44d288a293f7ba5b4075757784c422632892655d
--- /dev/null
+++ b/ee/spec/requests/api/graphql/audit_events/streaming/http/namespace_filters/delete_spec.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Delete a namespace filter for group level external audit event destinations', feature_category: :audit_events do
+ include GraphqlHelpers
+
+ let(:current_user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:subgroup) { create(:group, parent: group) }
+ let(:destination) { create(:external_audit_event_destination, group: group) }
+ let!(:filter) do
+ create(:audit_events_streaming_http_namespace_filter, external_audit_event_destination: destination,
+ namespace: subgroup)
+ end
+
+ let(:mutation) { graphql_mutation(:audit_events_streaming_http_namespace_filters_delete, input) }
+ let(:mutation_response) { graphql_mutation_response(:audit_events_streaming_http_namespace_filters_delete) }
+
+ let(:input) do
+ { namespaceFilterId: filter.to_gid }
+ end
+
+ subject(:mutate) { post_graphql_mutation(mutation, current_user: current_user) }
+
+ shared_examples 'does not delete the namespace filter' do
+ it do
+ expect(::Gitlab::Audit::Auditor).not_to receive(:audit)
+ .with(a_hash_including(name: 'delete_http_namespace_filter'))
+
+ expect { subject }.not_to change { destination.reload.namespace_filter }
+ end
+ end
+
+ context 'when feature is licensed' do
+ before do
+ stub_licensed_features(external_audit_events: true)
+ end
+
+ context 'when current user is group owner' do
+ before do
+ group.add_owner(current_user)
+ end
+
+ it 'deletes the filter', :aggregate_failures do
+ expect(::Gitlab::Audit::Auditor).to receive(:audit).with(a_hash_including(
+ name: 'delete_http_namespace_filter',
+ author: current_user,
+ scope: group,
+ target: destination,
+ message: "Delete namespace filter for http audit event streaming destination #{destination.name} " \
+ "and namespace #{subgroup.full_path}")).once.and_call_original
+
+ expect { mutate }.to change { AuditEvents::Streaming::HTTP::NamespaceFilter.count }.by(-1)
+
+ expect(destination.reload.namespace_filter).to be nil
+ expect_graphql_errors_to_be_empty
+ expect(mutation_response['errors']).to be_empty
+ expect(mutation_response['namespaceFilter']).to be nil
+ end
+ end
+
+ context 'when current user is a group maintainer' do
+ before do
+ group.add_maintainer(current_user)
+ end
+
+ it_behaves_like 'does not delete the namespace filter'
+ end
+ end
+
+ context 'when feature is not licensed' do
+ before do
+ stub_licensed_features(external_audit_events: false)
+ end
+
+ it_behaves_like 'a mutation on an unauthorized resource'
+
+ it_behaves_like 'does not delete the namespace filter'
+ end
+end