diff --git a/ee/app/graphql/mutations/audit_events/streaming/event_type_filters/create.rb b/ee/app/graphql/mutations/audit_events/streaming/event_type_filters/create.rb index 822508920375302eb106cb78f6f53a5e69422d3f..5cdfab4f9383f0e1e4b6d3388444f3db7841de32 100644 --- a/ee/app/graphql/mutations/audit_events/streaming/event_type_filters/create.rb +++ b/ee/app/graphql/mutations/audit_events/streaming/event_type_filters/create.rb @@ -14,7 +14,11 @@ class Create < BaseMutation argument :event_type_filters, [GraphQL::Types::String], required: true, - description: 'List of event type filters to add for streaming.' + description: 'List of event type filters to add for streaming.', + prepare: ->(filters, _ctx) do + filters.presence || (raise ::Gitlab::Graphql::Errors::ArgumentError, + 'event type filters must be present') + end field :event_type_filters, [GraphQL::Types::String], null: true, @@ -25,7 +29,8 @@ def resolve(destination_id:, event_type_filters:) response = ::AuditEvents::Streaming::EventTypeFilters::CreateService.new( destination: destination, - event_type_filters: event_type_filters + event_type_filters: event_type_filters, + current_user: current_user ).execute if response.success? diff --git a/ee/app/models/audit_events/external_audit_event_destination.rb b/ee/app/models/audit_events/external_audit_event_destination.rb index 131ccee8af21add5c442294bc53c391ab59d95fb..2d2248d8ac116dff52ac0060959cb2ec9e8408ef 100644 --- a/ee/app/models/audit_events/external_audit_event_destination.rb +++ b/ee/app/models/audit_events/external_audit_event_destination.rb @@ -29,6 +29,10 @@ def headers_hash { STREAMING_TOKEN_HEADER_KEY => verification_token }.merge(headers.map(&:to_hash).inject(:merge).to_h) end + def audit_details + destination_url + end + private def has_fewer_than_20_headers? diff --git a/ee/app/services/audit_events/streaming/event_type_filters/base_service.rb b/ee/app/services/audit_events/streaming/event_type_filters/base_service.rb new file mode 100644 index 0000000000000000000000000000000000000000..aa38f3ebec3b64a1af26f426750fb5b5e3bd417d --- /dev/null +++ b/ee/app/services/audit_events/streaming/event_type_filters/base_service.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module AuditEvents + module Streaming + module EventTypeFilters + class BaseService + attr_reader :destination, :event_type_filters, :current_user + + def initialize(destination:, event_type_filters:, current_user:) + @destination = destination + @event_type_filters = event_type_filters + @current_user = current_user + end + end + end + end +end diff --git a/ee/app/services/audit_events/streaming/event_type_filters/create_service.rb b/ee/app/services/audit_events/streaming/event_type_filters/create_service.rb index 7c4603c5eacac17f889e481840d9053e49d9e492..c3d39f4beb2c81a7b5a826f80650b1730c6f4bf4 100644 --- a/ee/app/services/audit_events/streaming/event_type_filters/create_service.rb +++ b/ee/app/services/audit_events/streaming/event_type_filters/create_service.rb @@ -3,17 +3,11 @@ module AuditEvents module Streaming module EventTypeFilters - class CreateService - attr_reader :destination, :event_type_filters - - def initialize(destination:, event_type_filters:) - @destination = destination - @event_type_filters = event_type_filters - end - + class CreateService < BaseService def execute begin create_event_type_filters! + log_audit_event rescue ActiveRecord::RecordInvalid => e return ServiceResponse.error(message: e.message) end @@ -30,6 +24,18 @@ def create_event_type_filters! end end end + + def log_audit_event + audit_context = { + name: 'event_type_filters_created', + author: current_user, + scope: destination.group, + target: destination, + message: "Created audit event type filter(s): #{event_type_filters.to_sentence}" + } + + ::Gitlab::Audit::Auditor.audit(audit_context) + end end end end diff --git a/ee/config/audit_events/types/event_type_filters_created.yml b/ee/config/audit_events/types/event_type_filters_created.yml new file mode 100644 index 0000000000000000000000000000000000000000..fe93963a2744b4881b139ae0cd395974c02099fa --- /dev/null +++ b/ee/config/audit_events/types/event_type_filters_created.yml @@ -0,0 +1,9 @@ +--- +name: event_type_filters_created +description: Event triggered when a new audit events streaming event type filter is created +introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/344848 +introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/113081 +feature_category: audit_events +milestone: '15.10' +saved_to_database: true +streamed: true diff --git a/ee/spec/graphql/mutations/audit_events/streaming/event_type_filters/create_spec.rb b/ee/spec/graphql/mutations/audit_events/streaming/event_type_filters/create_spec.rb index 9997309e89e5005b50ab4b8a7f887c43ad599909..a9bd76b9ba78c1570818d54cba56dc3ed62db75c 100644 --- a/ee/spec/graphql/mutations/audit_events/streaming/event_type_filters/create_spec.rb +++ b/ee/spec/graphql/mutations/audit_events/streaming/event_type_filters/create_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Mutations::AuditEvents::Streaming::EventTypeFilters::Create do +RSpec.describe Mutations::AuditEvents::Streaming::EventTypeFilters::Create, feature_category: :audit_events do let_it_be(:current_user) { create(:user) } let_it_be(:destination) { create(:external_audit_event_destination) } diff --git a/ee/spec/models/audit_events/external_audit_event_destination_spec.rb b/ee/spec/models/audit_events/external_audit_event_destination_spec.rb index 3ac56baa5412437db1a3eb52e95c4a0d066a7214..34cc108709dba1c9db8b496e2f5fc85b879c9d5b 100644 --- a/ee/spec/models/audit_events/external_audit_event_destination_spec.rb +++ b/ee/spec/models/audit_events/external_audit_event_destination_spec.rb @@ -93,4 +93,10 @@ it_behaves_like 'includes Limitable concern' do subject { build(:external_audit_event_destination, group: create(:group)) } end + + describe '#audit_details' do + it "equals to the destination url" do + expect(destination.audit_details).to eq(destination.destination_url) + end + end end diff --git a/ee/spec/requests/api/graphql/audit_events/streaming/event_type_filters/create_spec.rb b/ee/spec/requests/api/graphql/audit_events/streaming/event_type_filters/create_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..bdd1033970fd30245216ac845a7190dee639a145 --- /dev/null +++ b/ee/spec/requests/api/graphql/audit_events/streaming/event_type_filters/create_spec.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Create an audit event type filter', feature_category: :audit_events do + include GraphqlHelpers + + let_it_be(:destination) { create(:external_audit_event_destination) } + let_it_be(:event_type_filter) do + create(:audit_events_streaming_event_type_filter, external_audit_event_destination: destination, + audit_event_type: 'filter_1') + end + + let_it_be(:user) { create(:user) } + + let(:current_user) { user } + let(:group) { destination.group } + let(:mutation_name) { :audit_events_streaming_destination_events_add } + let(:mutation) { graphql_mutation(mutation_name, input) } + let(:mutation_response) { graphql_mutation_response(mutation_name) } + let(:input) { { destinationId: destination.to_gid, eventTypeFilters: ['filter_2'] } } + + context 'when unlicensed' do + subject { post_graphql_mutation(mutation, current_user: user) } + + before do + stub_licensed_features(external_audit_events: false) + end + + it_behaves_like 'a mutation on an unauthorized resource' + end + + context 'when licensed' do + subject { post_graphql_mutation(mutation, current_user: user) } + + before do + stub_licensed_features(external_audit_events: true) + end + + context 'when current user is a group maintainer' do + before do + group.add_maintainer(user) + end + + it_behaves_like 'a mutation that returns top-level errors', + errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] + end + + context 'when current user is a group developer' do + before do + group.add_developer(user) + end + + it_behaves_like 'a mutation that returns top-level errors', + errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] + end + + context 'when current user is a group guest' do + before do + group.add_guest(user) + end + + it_behaves_like 'a mutation that returns top-level errors', + errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] + end + + context 'when current user is a group user' do + before do + group.add_owner(user) + end + + it 'returns success response', :aggregate_failures do + subject + + response = mutation_response + expect(response["errors"]).to be_empty + expect(response["eventTypeFilters"]).to eq(destination.event_type_filters.pluck(:audit_event_type)) + end + + context 'when event type filters in input in empty' do + let(:input) { { destinationId: destination.to_gid, eventTypeFilters: [] } } + + it 'returns graphql error' do + subject + + expect(graphql_errors).to include(a_hash_including('message' => 'event type filters must be present')) + end + end + end + end +end diff --git a/ee/spec/services/audit_events/streaming/event_type_filters/create_service_spec.rb b/ee/spec/services/audit_events/streaming/event_type_filters/create_service_spec.rb index af76b17a27be71c8d3dbd1c4bf34a255e05f2944..bc9acd7d4bfc216a34ae94ae9575559480969c95 100644 --- a/ee/spec/services/audit_events/streaming/event_type_filters/create_service_spec.rb +++ b/ee/spec/services/audit_events/streaming/event_type_filters/create_service_spec.rb @@ -2,15 +2,17 @@ require 'spec_helper' -RSpec.describe AuditEvents::Streaming::EventTypeFilters::CreateService do +RSpec.describe AuditEvents::Streaming::EventTypeFilters::CreateService, feature_category: :audit_events do let_it_be(:destination) { create(:external_audit_event_destination) } let_it_be(:event_type_filters) { ['filter_1'] } + let_it_be(:user) { create(:user) } let(:expected_error) { [] } subject(:response) do described_class.new(destination: destination, - event_type_filters: event_type_filters).execute + event_type_filters: event_type_filters, + current_user: user).execute end describe '#execute' do @@ -21,6 +23,21 @@ expect(response).to be_success expect(response.errors).to match_array(expected_error) end + + it 'creates audit event', :aggregate_failures do + audit_context = { + name: 'event_type_filters_created', + author: user, + scope: destination.group, + target: destination, + message: "Created audit event type filter(s): filter_1" + } + + expect(::Gitlab::Audit::Auditor).to receive(:audit).with(audit_context) + .and_call_original + + expect { subject }.to change { AuditEvent.count }.by(1) + end end context 'when record is invalid' do @@ -36,6 +53,10 @@ expect { subject }.not_to change { destination.event_type_filters.count } expect(response.errors).to match_array([expected_error]) end + + it 'does not create audit event' do + expect { subject }.not_to change { AuditEvent.count } + end end end end