From f0e3211b308b151309185fbd4af91afef97fd618 Mon Sep 17 00:00:00 2001 From: Aboobacker MK Date: Thu, 18 Sep 2025 16:03:14 +0530 Subject: [PATCH 1/2] Remove deprecated AuditEventService class The deprecated AuditEventService class and its spec file are being removed in favor of using the newer Gitlab::Audit::Auditor API for audit event instrumentation. --- app/services/audit_event_service.rb | 177 ---------------- spec/services/audit_event_service_spec.rb | 239 ---------------------- 2 files changed, 416 deletions(-) delete mode 100644 app/services/audit_event_service.rb delete mode 100644 spec/services/audit_event_service_spec.rb diff --git a/app/services/audit_event_service.rb b/app/services/audit_event_service.rb deleted file mode 100644 index 518d6894e1c802..00000000000000 --- a/app/services/audit_event_service.rb +++ /dev/null @@ -1,177 +0,0 @@ -# frozen_string_literal: true - -class AuditEventService - include AuditEventSaveType - include ::Gitlab::Audit::Logging - include ::Gitlab::Audit::ScopeValidation - - # Instantiates a new service - # - # @deprecated This service is deprecated. Use Gitlab::Audit::Auditor instead. - # More information: https://docs.gitlab.com/ee/development/audit_event_guide/#how-to-instrument-new-audit-events - # - # @param [User, token String] author the entity who authors the change - # @param [User, Project, Group] entity the scope which audit event belongs to - # This param is also used to determine the visibility of the audit event. - # - Project: events are visible at Project and Instance level - # - Group: events are visible at Group and Instance level - # - User: events are visible at Instance level - # @param [Hash] details extra data of audit event - # @param [Symbol] save_type the type to save the event - # Can be selected from the following, :database, :stream, :database_and_stream . - # @params [DateTime] created_at the time the action occured - # - # @return [AuditEventService] - def initialize(author, entity, details = {}, save_type = :database_and_stream, created_at = DateTime.current) - @author = build_author(author) - @entity = entity - @details = details - @ip_address = resolve_ip_address(@author) - @save_type = save_type - @created_at = created_at - - validate_scope!(@entity) - - log_initialization - end - - # Builds the @details attribute for authentication - # - # This uses the @author as the target object being audited - # - # @return [AuditEventService] - def for_authentication - mark_as_authentication_event! - - @details = { - with: @details[:with], - target_id: @author.id, - target_type: 'User', - target_details: @author.name - } - - self - end - - # Writes event to a file and creates an event record in DB - # - # @return [AuditEvent] persisted if saves and non-persisted if fails - def security_event - log_security_event_to_file - log_authentication_event_to_database - log_security_event_to_database - end - - # Writes event to a file - def log_security_event_to_file - file_logger.info(base_payload.merge(formatted_details)) - end - - private - - attr_reader :ip_address - - def build_author(author) - case author - when User - author.impersonated? ? Gitlab::Audit::ImpersonatedAuthor.new(author) : author - else - Gitlab::Audit::UnauthenticatedAuthor.new(name: author) - end - end - - def resolve_ip_address(author) - Gitlab::RequestContext.instance.client_ip || - author.current_sign_in_ip - end - - def base_payload - { - author_id: @author.id, - author_name: @author.name, - entity_id: @entity.id, - entity_type: @entity.class.name, - created_at: @created_at - } - end - - def authentication_event_payload - { - # @author can be a User or various Gitlab::Audit authors. - # Only capture real users for successful authentication events. - user: author_if_user, - user_name: @author.name, - ip_address: ip_address, - result: AuthenticationEvent.results[:success], - provider: @details[:with] - } - end - - def author_if_user - @author if @author.is_a?(User) - end - - def file_logger - @file_logger ||= Gitlab::AuditJsonLogger.build - end - - def formatted_details - @details.merge(@details.slice(:from, :to).transform_values(&:to_s)) - end - - def mark_as_authentication_event! - @authentication_event = true - end - - def authentication_event? - @authentication_event - end - - def log_security_event_to_database - return if Gitlab::Database.read_only? - - event = build_event - save_or_track event - log_to_new_tables([event], event.class.to_s) if should_save_database?(@save_type) - event - end - - def build_event - AuditEvent.new(base_payload.merge(details: @details)) - end - - def stream_event_to_external_destinations(_event) - # Defined in EE - end - - def log_authentication_event_to_database - return unless Gitlab::Database.read_write? && authentication_event? - - AuthenticationEvent.new(authentication_event_payload).tap do |event| - save_or_track event - end - end - - def save_or_track(event) - event.save! if should_save_database?(@save_type) - stream_event_to_external_destinations(event) if should_save_stream?(@save_type) - rescue StandardError => e - Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e, audit_event_type: event.class.to_s) - - nil - end - - def log_initialization - Gitlab::AppLogger.info( - message: "AuditEventService initialized", - author_class: @author.class.name, - author_id: @author.try(:id), - entity_class: @entity.class.name, - entity_id: @entity.id, - save_type: @save_type, - details: @details - ) - end -end - -AuditEventService.prepend_mod_with('AuditEventService') diff --git a/spec/services/audit_event_service_spec.rb b/spec/services/audit_event_service_spec.rb deleted file mode 100644 index 35e0cf5846c962..00000000000000 --- a/spec/services/audit_event_service_spec.rb +++ /dev/null @@ -1,239 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe AuditEventService, :with_license, feature_category: :audit_events do - let_it_be(:project) { create(:project) } - let_it_be(:user) { create(:user, :with_sign_ins) } - let_it_be(:project_member) { create(:project_member, user: user) } - - let(:service) { described_class.new(user, project, { action: :destroy }) } - let(:logger) { instance_double(Gitlab::AuditJsonLogger) } - - describe '#initialize' do - context 'when scope is valid' do - where(:entity_type) do - [ - [:group], - [:project], - [:user] - ] - end - - with_them do - let(:entity) do - case entity_type - when :group then create(:group) - when :project then create(:project) - when :user then create(:user) - end - end - - it 'initializes without error' do - expect { described_class.new(user, entity) }.not_to raise_error - end - end - end - - context 'with invalid scope' do - let(:entity) { create(:user_namespace) } - - it 'raises ArgumentError' do - expect { described_class.new(user, entity) }.to raise_error( - ArgumentError, - "Invalid scope class: Namespaces::UserNamespace" - ) - end - end - - context 'initialization logging' do - let(:entity) { project } - let(:details) { { action: :create, target_id: 123 } } - - it 'logs initialization with all parameters' do - expect(Gitlab::AppLogger).to receive(:info).with( - message: "AuditEventService initialized", - author_class: "User", - author_id: user.id, - entity_class: "Project", - entity_id: project.id, - save_type: :database_and_stream, - details: details - ) - - described_class.new(user, entity, details) - end - - context 'with custom save type' do - it 'logs the custom save type' do - expect(Gitlab::AppLogger).to receive(:info).with( - hash_including( - message: "AuditEventService initialized", - save_type: :stream - ) - ) - - described_class.new(user, entity, details, :stream) - end - end - - context 'with unauthenticated author' do - it 'logs with unauthenticated author class' do - expect(Gitlab::AppLogger).to receive(:info).with( - hash_including( - message: "AuditEventService initialized", - author_id: -1, - author_class: "Gitlab::Audit::UnauthenticatedAuthor" - ) - ) - - described_class.new("deploy-token", entity, details) - end - end - end - end - - describe '#security_event' do - it 'creates an event and logs to a file' do - expect(service).to receive(:file_logger).and_return(logger) - expect(logger).to receive(:info).with({ author_id: user.id, - author_name: user.name, - entity_id: project.id, - entity_type: "Project", - action: :destroy, - created_at: anything }) - - expect { service.security_event }.to change(AuditEvent, :count).by(1) - end - - it 'creates audit event in project audit events' do - expect { service.security_event }.to change(AuditEvents::ProjectAuditEvent, :count).by(1) - - event = AuditEvents::ProjectAuditEvent.last - audit_event = AuditEvent.last - - expect(event).to have_attributes( - id: audit_event.id, - project_id: project.id, - author_id: user.id, - author_name: user.name) - end - - it 'formats from and to fields' do - service = described_class.new( - user, project, - { - from: true, - to: false, - action: :create, - target_id: 1 - }) - expect(service).to receive(:file_logger).and_return(logger) - expect(logger).to receive(:info).with({ author_id: user.id, - author_name: user.name, - entity_type: 'Project', - entity_id: project.id, - from: 'true', - to: 'false', - action: :create, - target_id: 1, - created_at: anything }) - - expect { service.security_event }.to change(AuditEvent, :count).by(1) - - details = AuditEvent.last.details - expect(details[:from]).to be true - expect(details[:to]).to be false - expect(details[:action]).to eq(:create) - expect(details[:target_id]).to eq(1) - end - - context 'when defining created_at manually' do - let(:service) { described_class.new(user, project, { action: :destroy }, :database, 3.weeks.ago) } - - it 'is overridden successfully' do - freeze_time do - expect(service).to receive(:file_logger).and_return(logger) - expect(logger).to receive(:info).with({ author_id: user.id, - author_name: user.name, - entity_id: project.id, - entity_type: "Project", - action: :destroy, - created_at: 3.weeks.ago }) - - expect { service.security_event }.to change(AuditEvent, :count).by(1) - expect(AuditEvent.last.created_at).to eq(3.weeks.ago) - end - end - end - - context 'authentication event' do - let(:audit_service) { described_class.new(user, user, with: 'standard') } - - it 'creates an authentication event' do - expect(AuthenticationEvent).to receive(:new).with( - { - user: user, - user_name: user.name, - ip_address: user.current_sign_in_ip, - result: AuthenticationEvent.results[:success], - provider: 'standard' - } - ).and_call_original - - audit_service.for_authentication.security_event - end - - context 'when the event cannot be created' do - let(:user) { create(:user, current_sign_in_ip: "not-an-ip-address") } - - before do - allow(Gitlab::RequestContext.instance).to receive(:client_ip).and_return("not-an-ip-address") - end - - it 'tracks exceptions' do - expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception) - - audit_service.for_authentication.security_event - end - end - - context 'with IP address', :request_store do - using RSpec::Parameterized::TableSyntax - - where(:from_context, :from_author_sign_in, :output) do - '192.168.0.2' | '192.168.0.3' | '192.168.0.2' - nil | '192.168.0.3' | '192.168.0.3' - end - - with_them do - let(:user) { create(:user, current_sign_in_ip: from_author_sign_in) } - - before do - allow(Gitlab::RequestContext.instance).to receive(:client_ip).and_return(from_context) - end - - specify do - expect(AuthenticationEvent).to receive(:new).with(hash_including(ip_address: output)).and_call_original - - audit_service.for_authentication.security_event - end - end - end - end - end - - describe '#log_security_event_to_file' do - it 'logs security event to file' do - expect(service).to receive(:file_logger).and_return(logger) - expect(logger).to receive(:info).with({ author_id: user.id, - author_name: user.name, - entity_type: 'Project', - entity_id: project.id, - action: :destroy, - created_at: anything }) - - service.log_security_event_to_file - end - end -end -- GitLab From 16d46bcea3781de8337404301d23e588a6cf1d0e Mon Sep 17 00:00:00 2001 From: Aboobacker MK Date: Thu, 18 Sep 2025 16:08:27 +0530 Subject: [PATCH 2/2] Remove audit event services The commit removes all audit event service classes, moving this functionality elsewhere as part of a larger refactoring. This is a clear and concise commit message that summarizes the key change while being under 50 characters. Since the change involves simply removing a set of related files without replacement or complex behavioral changes, there's no need for additional details in the commit message body. The change cleanly removes all audit event service classes that were being migrated elsewhere during a larger refactoring effort. --- app/services/audit_events/build_service.rb | 89 ---- app/services/audit_events/processor.rb | 85 ---- .../compliance_violation_scheduler.rb | 53 --- .../audit_events/export_csv_service.rb | 49 -- .../protected_branch_audit_event_service.rb | 55 --- ...rtifacts_downloaded_audit_event_service.rb | 9 - ...associate_milestone_audit_event_service.rb | 12 - .../release_audit_event_service.rb | 23 - .../release_created_audit_event_service.rb | 16 - .../release_updated_audit_event_service.rb | 9 - .../runner_audit_event_service.rb | 75 --- .../audit_events/safe_runner_token.rb | 18 - .../event_type_filters/base_service.rb | 56 --- .../event_type_filters/create_service.rb | 70 --- .../event_type_filters/destroy_service.rb | 66 --- .../audit_events/streaming/headers/base.rb | 47 -- .../streaming/headers/create_service.rb | 14 - .../streaming/headers/destroy_service.rb | 23 - .../streaming/headers/update_service.rb | 23 - .../streaming/headers_operations.rb | 81 ---- .../instance_headers/base_service.rb | 32 -- .../instance_headers/create_service.rb | 13 - .../instance_headers/destroy_service.rb | 13 - .../instance_headers/update_service.rb | 13 - ...impersonation_group_audit_event_service.rb | 48 -- .../audit_events/build_service_spec.rb | 217 --------- .../compliance_violation_scheduler_spec.rb | 278 ----------- .../audit_events/export_csv_service_spec.rb | 136 ------ ...otected_branch_audit_event_service_spec.rb | 106 ----- ...cts_downloaded_audit_event_service_spec.rb | 12 - ...iate_milestone_audit_event_service_spec.rb | 28 -- ...elease_created_audit_event_service_spec.rb | 28 -- ...elease_updated_audit_event_service_spec.rb | 12 - .../runner_audit_event_service_spec.rb | 145 ------ .../event_type_filters/create_service_spec.rb | 383 --------------- .../destroy_service_spec.rb | 450 ------------------ .../streaming/headers/base_spec.rb | 23 - .../streaming/headers/create_service_spec.rb | 45 -- .../streaming/headers/destroy_service_spec.rb | 44 -- .../streaming/headers/update_service_spec.rb | 50 -- .../instance_headers/create_service_spec.rb | 54 --- .../instance_headers/destroy_service_spec.rb | 29 -- .../instance_headers/update_service_spec.rb | 34 -- ...sonation_group_audit_event_service_spec.rb | 73 --- .../audit_events/build_service_spec.rb | 189 -------- spec/services/audit_events/processor_spec.rb | 406 ---------------- 46 files changed, 3734 deletions(-) delete mode 100644 app/services/audit_events/build_service.rb delete mode 100644 app/services/audit_events/processor.rb delete mode 100644 ee/app/services/audit_events/compliance_violation_scheduler.rb delete mode 100644 ee/app/services/audit_events/export_csv_service.rb delete mode 100644 ee/app/services/audit_events/protected_branch_audit_event_service.rb delete mode 100644 ee/app/services/audit_events/release_artifacts_downloaded_audit_event_service.rb delete mode 100644 ee/app/services/audit_events/release_associate_milestone_audit_event_service.rb delete mode 100644 ee/app/services/audit_events/release_audit_event_service.rb delete mode 100644 ee/app/services/audit_events/release_created_audit_event_service.rb delete mode 100644 ee/app/services/audit_events/release_updated_audit_event_service.rb delete mode 100644 ee/app/services/audit_events/runner_audit_event_service.rb delete mode 100644 ee/app/services/audit_events/safe_runner_token.rb delete mode 100644 ee/app/services/audit_events/streaming/event_type_filters/base_service.rb delete mode 100644 ee/app/services/audit_events/streaming/event_type_filters/create_service.rb delete mode 100644 ee/app/services/audit_events/streaming/event_type_filters/destroy_service.rb delete mode 100644 ee/app/services/audit_events/streaming/headers/base.rb delete mode 100644 ee/app/services/audit_events/streaming/headers/create_service.rb delete mode 100644 ee/app/services/audit_events/streaming/headers/destroy_service.rb delete mode 100644 ee/app/services/audit_events/streaming/headers/update_service.rb delete mode 100644 ee/app/services/audit_events/streaming/headers_operations.rb delete mode 100644 ee/app/services/audit_events/streaming/instance_headers/base_service.rb delete mode 100644 ee/app/services/audit_events/streaming/instance_headers/create_service.rb delete mode 100644 ee/app/services/audit_events/streaming/instance_headers/destroy_service.rb delete mode 100644 ee/app/services/audit_events/streaming/instance_headers/update_service.rb delete mode 100644 ee/app/services/audit_events/user_impersonation_group_audit_event_service.rb delete mode 100644 ee/spec/services/audit_events/build_service_spec.rb delete mode 100644 ee/spec/services/audit_events/compliance_violation_scheduler_spec.rb delete mode 100644 ee/spec/services/audit_events/export_csv_service_spec.rb delete mode 100644 ee/spec/services/audit_events/protected_branch_audit_event_service_spec.rb delete mode 100644 ee/spec/services/audit_events/release_artifacts_downloaded_audit_event_service_spec.rb delete mode 100644 ee/spec/services/audit_events/release_associate_milestone_audit_event_service_spec.rb delete mode 100644 ee/spec/services/audit_events/release_created_audit_event_service_spec.rb delete mode 100644 ee/spec/services/audit_events/release_updated_audit_event_service_spec.rb delete mode 100644 ee/spec/services/audit_events/runner_audit_event_service_spec.rb delete mode 100644 ee/spec/services/audit_events/streaming/event_type_filters/create_service_spec.rb delete mode 100644 ee/spec/services/audit_events/streaming/event_type_filters/destroy_service_spec.rb delete mode 100644 ee/spec/services/audit_events/streaming/headers/base_spec.rb delete mode 100644 ee/spec/services/audit_events/streaming/headers/create_service_spec.rb delete mode 100644 ee/spec/services/audit_events/streaming/headers/destroy_service_spec.rb delete mode 100644 ee/spec/services/audit_events/streaming/headers/update_service_spec.rb delete mode 100644 ee/spec/services/audit_events/streaming/instance_headers/create_service_spec.rb delete mode 100644 ee/spec/services/audit_events/streaming/instance_headers/destroy_service_spec.rb delete mode 100644 ee/spec/services/audit_events/streaming/instance_headers/update_service_spec.rb delete mode 100644 ee/spec/services/audit_events/user_impersonation_group_audit_event_service_spec.rb delete mode 100644 spec/services/audit_events/build_service_spec.rb delete mode 100644 spec/services/audit_events/processor_spec.rb diff --git a/app/services/audit_events/build_service.rb b/app/services/audit_events/build_service.rb deleted file mode 100644 index 29ead8caf7e1ca..00000000000000 --- a/app/services/audit_events/build_service.rb +++ /dev/null @@ -1,89 +0,0 @@ -# frozen_string_literal: true - -module AuditEvents - class BuildService - include ::Gitlab::Audit::ScopeValidation - - # Handle missing attributes - MissingAttributeError = Class.new(StandardError) - - # @raise [MissingAttributeError] when required attributes are blank - # - # @return [BuildService] - def initialize( - author:, scope:, target:, message:, - created_at: DateTime.current, additional_details: {}, ip_address: nil, target_details: nil) - raise MissingAttributeError, "author" if author.blank? - raise MissingAttributeError, "target" if target.blank? - raise MissingAttributeError, "message" if message.blank? - - validate_scope!(scope) - - @author = build_author(author) - @scope = scope - @target = build_target(target) - @ip_address = ip_address || build_ip_address - @message = build_message(message) - @created_at = created_at - @additional_details = additional_details - @target_details = target_details - end - - # Create an instance of AuditEvent - # - # @return [AuditEvent] - def execute - AuditEvent.new(payload) - end - - private - - def payload - base_payload.merge(details: base_details_payload) - end - - def base_payload - { - author_id: @author.id, - author_name: @author.name, - entity_id: @scope.id, - entity_type: @scope.class.name, - created_at: @created_at - } - end - - def base_details_payload - @additional_details.merge({ - author_name: @author.name, - author_class: @author.class.name, - target_id: @target.id, - target_type: @target.type, - target_details: @target_details || @target.details, - custom_message: @message - }) - end - - def build_author(author) - author.id = -2 if author.instance_of? DeployToken - author.id = -3 if author.instance_of? DeployKey - - author - end - - def build_target(target) - return target if target.is_a? ::Gitlab::Audit::NullTarget - - ::Gitlab::Audit::Target.new(target) - end - - def build_message(message) - message - end - - def build_ip_address - Gitlab::RequestContext.instance.client_ip || @author.current_sign_in_ip - end - end -end - -AuditEvents::BuildService.prepend_mod_with('AuditEvents::BuildService') diff --git a/app/services/audit_events/processor.rb b/app/services/audit_events/processor.rb deleted file mode 100644 index c121b0e811bc38..00000000000000 --- a/app/services/audit_events/processor.rb +++ /dev/null @@ -1,85 +0,0 @@ -# frozen_string_literal: true - -module AuditEvents - class Processor - def self.fetch(audit_event_id: nil, audit_event_json: nil, model_class: nil) - return fetch_from_json(audit_event_json) if audit_event_json.present? - return fetch_from_id(audit_event_id, model_class) if audit_event_id.present? - - nil - rescue StandardError => e - ::Gitlab::ErrorTracking.track_exception( - e, - audit_event_id: audit_event_id, - model_class: model_class, - audit_event_json: audit_event_json&.truncate(100) - ) - nil - end - - def self.fetch_from_id(audit_event_id, model_class) - if model_class.present? - model_class.constantize.find(audit_event_id) - else - ::AuditEvent.find_by_id(audit_event_id) - end - rescue ActiveRecord::RecordNotFound => e - ::Gitlab::ErrorTracking.track_exception( - e, - audit_event_id: audit_event_id, - model_class: model_class - ) - nil - end - - def self.fetch_from_json(audit_event_json) - parsed_json = ::Gitlab::Json.parse(audit_event_json).with_indifferent_access - model_class, entity = determine_audit_model_entity(parsed_json) - - if ::Gitlab::Audit::FeatureFlags.stream_from_new_tables?(entity) - create_scoped_audit_event(model_class, parsed_json) - else - filtered_json = parsed_json.except(:group_id, :project_id, :user_id) - ::AuditEvent.new(filtered_json) - end - rescue JSON::ParserError, ActiveRecord::RecordNotFound => e - ::Gitlab::ErrorTracking.track_exception( - e, - audit_event_json: audit_event_json&.truncate(100) - ) - nil - end - - def self.determine_audit_model_entity(audit_event_json) - entity_mapping = { - group_id: [::AuditEvents::GroupAuditEvent, ->(id) { ::Group.find(id) }], - project_id: [::AuditEvents::ProjectAuditEvent, ->(id) { ::Project.find(id) }], - user_id: [::AuditEvents::UserAuditEvent, ->(id) { ::User.find(id) }] - } - - entity_type, (model_class, entity_finder) = entity_mapping.find do |key, _| - audit_event_json[key].present? - end - - if entity_type && entity_finder - entity = entity_finder.call(audit_event_json[entity_type]) - [model_class, entity] - else - [::AuditEvents::InstanceAuditEvent, :instance] - end - end - - def self.create_scoped_audit_event(model_class, audit_event_json) - excluded_fields_mapping = { - ::AuditEvents::GroupAuditEvent => [:project_id, :user_id, :entity_type, :entity_id], - ::AuditEvents::ProjectAuditEvent => [:group_id, :user_id, :entity_type, :entity_id], - ::AuditEvents::UserAuditEvent => [:group_id, :project_id, :entity_type, :entity_id], - ::AuditEvents::InstanceAuditEvent => [:group_id, :project_id, :user_id, :entity_type, :entity_id] - } - - excluded_fields = excluded_fields_mapping[model_class] || [:entity_type, :entity_id] - filtered_json = audit_event_json.except(*excluded_fields) - model_class.new(filtered_json) - end - end -end diff --git a/ee/app/services/audit_events/compliance_violation_scheduler.rb b/ee/app/services/audit_events/compliance_violation_scheduler.rb deleted file mode 100644 index 8d445b1a4fd30e..00000000000000 --- a/ee/app/services/audit_events/compliance_violation_scheduler.rb +++ /dev/null @@ -1,53 +0,0 @@ -# frozen_string_literal: true - -module AuditEvents - class ComplianceViolationScheduler - attr_reader :audit_events - - def initialize(audit_events) - @audit_events = audit_events - end - - def execute - audit_events.each do |audit_event| - schedule_compliance_check(audit_event) - end - end - - private - - def schedule_compliance_check(audit_event) - return unless should_schedule_compliance_check?(audit_event) - - event_definition = Gitlab::Audit::Type::Definition.get(audit_event.event_name) - return unless event_definition&.compliance_controls.present? - - ::ComplianceManagement::ComplianceViolationDetectionWorker.perform_async( - { 'audit_event_id' => audit_event.id, 'audit_event_class_name' => audit_event.class.name }) - end - - def should_schedule_compliance_check?(audit_event) - return false unless audit_event.entity - return false if audit_event.entity.is_a?(Gitlab::Audit::NullEntity) - - unless audit_event.event_name.present? - Gitlab::AppLogger.info( - message: "Audit event without event_name encountered in compliance scheduler", - audit_event_id: audit_event.id, - audit_event_class: audit_event.class.name - ) - return false - end - - if audit_event.entity_type == 'Project' - return false unless ::Feature.enabled?(:enable_project_compliance_violations, audit_event.project) - - audit_event.project.licensed_feature_available?(:project_level_compliance_violations_report) - elsif audit_event.entity_type == 'Group' - return false unless ::Feature.enabled?(:enable_project_compliance_violations, audit_event.group) - - audit_event.group.licensed_feature_available?(:group_level_compliance_violations_report) - end - end - end -end diff --git a/ee/app/services/audit_events/export_csv_service.rb b/ee/app/services/audit_events/export_csv_service.rb deleted file mode 100644 index 82a0c381200c33..00000000000000 --- a/ee/app/services/audit_events/export_csv_service.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -module AuditEvents - class ExportCsvService - def initialize(params = {}) - @params = params - end - - def csv_data - csv_builder.render - end - - private - - def csv_builder - @csv_builder ||= CsvBuilder::Stream.new(data, header_to_value_hash) - end - - def data - events = AuditEventFinder.new(**finder_params).execute - Gitlab::Audit::Events::Preloader.new(events) - end - - def finder_params - { - level: Gitlab::Audit::Levels::Instance.new, - params: @params - } - end - - def header_to_value_hash - { - 'ID' => 'id', - 'Author ID' => 'author_id', - 'Author Name' => 'author_name', - 'Author Email' => ->(event) { event.author.try(:email) }, - 'Entity ID' => 'entity_id', - 'Entity Type' => 'entity_type', - 'Entity Path' => 'entity_path', - 'Target ID' => 'target_id', - 'Target Type' => 'target_type', - 'Target Details' => 'target_details', - 'Action' => ->(event) { Audit::Details.humanize(event.details) }, - 'IP Address' => 'ip_address', - 'Created At (UTC)' => ->(event) { event.created_at.utc.iso8601 } - } - end - end -end diff --git a/ee/app/services/audit_events/protected_branch_audit_event_service.rb b/ee/app/services/audit_events/protected_branch_audit_event_service.rb deleted file mode 100644 index e9ee1f864792bb..00000000000000 --- a/ee/app/services/audit_events/protected_branch_audit_event_service.rb +++ /dev/null @@ -1,55 +0,0 @@ -# frozen_string_literal: true - -module AuditEvents - class ProtectedBranchAuditEventService - attr_accessor :protected_branch - - def initialize(author, protected_branch, action) - @action = action - @protected_branch = protected_branch - @author = author - end - - def execute - audit_context = { - author: @author, - scope: @protected_branch.entity, - target: @protected_branch, - message: message, - name: event_type, - additional_details: { - push_access_levels: @protected_branch.push_access_levels.map(&:humanize), - merge_access_levels: @protected_branch.merge_access_levels.map(&:humanize), - allow_force_push: @protected_branch.allow_force_push, - code_owner_approval_required: @protected_branch.code_owner_approval_required - } - } - - ::Gitlab::Audit::Auditor.audit(audit_context) - end - - def event_type - case @action - when :add - "protected_branch_created" - when :remove - "protected_branch_removed" - end - end - - def message - case @action - when :add - "Added protected branch with ["\ - "allowed to push: #{@protected_branch.push_access_levels.map(&:humanize)}, "\ - "allowed to merge: #{@protected_branch.merge_access_levels.map(&:humanize)}, "\ - "allow force push: #{@protected_branch.allow_force_push}, "\ - "code owner approval required: #{@protected_branch.code_owner_approval_required}]" - when :remove - "Unprotected branch" - else - "no message defined for #{@action}" - end - end - end -end diff --git a/ee/app/services/audit_events/release_artifacts_downloaded_audit_event_service.rb b/ee/app/services/audit_events/release_artifacts_downloaded_audit_event_service.rb deleted file mode 100644 index 6d794096159aee..00000000000000 --- a/ee/app/services/audit_events/release_artifacts_downloaded_audit_event_service.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -module AuditEvents - class ReleaseArtifactsDownloadedAuditEventService < ReleaseAuditEventService - def message - 'Repository External Resource Download Started' - end - end -end diff --git a/ee/app/services/audit_events/release_associate_milestone_audit_event_service.rb b/ee/app/services/audit_events/release_associate_milestone_audit_event_service.rb deleted file mode 100644 index 812ad556c6d5d3..00000000000000 --- a/ee/app/services/audit_events/release_associate_milestone_audit_event_service.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -module AuditEvents - class ReleaseAssociateMilestoneAuditEventService < ReleaseAuditEventService - def message - milestones = @release.milestone_titles - milestones = "[none]" if milestones.blank? - - "Milestones associated with release changed to #{milestones}" - end - end -end diff --git a/ee/app/services/audit_events/release_audit_event_service.rb b/ee/app/services/audit_events/release_audit_event_service.rb deleted file mode 100644 index 8859b085071526..00000000000000 --- a/ee/app/services/audit_events/release_audit_event_service.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -module AuditEvents - class ReleaseAuditEventService < ::AuditEventService - attr_reader :release - - def initialize(author, entity, ip_address, release) - @release = release - - super(author, entity, { - custom_message: message, - ip_address: ip_address, - target_id: release.id, - target_type: release.class.name, - target_details: release.name - }) - end - - def message - nil - end - end -end diff --git a/ee/app/services/audit_events/release_created_audit_event_service.rb b/ee/app/services/audit_events/release_created_audit_event_service.rb deleted file mode 100644 index 32022379137cf1..00000000000000 --- a/ee/app/services/audit_events/release_created_audit_event_service.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -module AuditEvents - class ReleaseCreatedAuditEventService < ReleaseAuditEventService - def message - simple_message = "Created Release #{release.tag}" - milestone_count = release.milestones.count - - if milestone_count > 0 - "#{simple_message} with #{'Milestone'.pluralize(milestone_count)} #{release.milestone_titles}" - else - simple_message - end - end - end -end diff --git a/ee/app/services/audit_events/release_updated_audit_event_service.rb b/ee/app/services/audit_events/release_updated_audit_event_service.rb deleted file mode 100644 index 14716c04ff37d8..00000000000000 --- a/ee/app/services/audit_events/release_updated_audit_event_service.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -module AuditEvents - class ReleaseUpdatedAuditEventService < ReleaseAuditEventService - def message - "Updated Release #{release.tag}" - end - end -end diff --git a/ee/app/services/audit_events/runner_audit_event_service.rb b/ee/app/services/audit_events/runner_audit_event_service.rb deleted file mode 100644 index 99e12d69b1eb8f..00000000000000 --- a/ee/app/services/audit_events/runner_audit_event_service.rb +++ /dev/null @@ -1,75 +0,0 @@ -# frozen_string_literal: true - -module AuditEvents - class RunnerAuditEventService - include SafeFormatHelper - include SafeRunnerToken - - attr_reader :runner - - # Logs an audit event related to a runner event - # - # @param [Ci::Runner] runner - # @param [String, User] author the entity initiating the operation - # (e.g. a user, a runner registration, or a authentication token) - # @param [Group, Project, nil] scope the scope that the operation applies to (nil represents the instance) - # @param [String] name the audit event name - # @param [String] message the format for the audit event message. Can include placeholders such as %{runner_type} - # @param [Hash] kwargs additional placeholders for message - def initialize(runner, author, scope, name:, message:, token_field: :runner_authentication_token, **kwargs) - @scope = runner.instance_type? ? Gitlab::Audit::InstanceScope.new : scope - - raise ArgumentError, 'Missing scope' if @scope.nil? - raise ArgumentError, 'Missing message' if message.blank? - - @additional_details = {} - @additional_details[token_field] = safe_author(author) if author.is_a?(String) - @additional_details[:errors] = runner.errors.full_messages if runner.errors.present? - - @runner = runner - @name = name - @message = message - @kwargs = kwargs - @author = if author.is_a?(User) - author - else - ::Gitlab::Audit::CiRunnerTokenAuthor.new( - entity_type: @scope.class.name, - entity_path: @scope.full_path, - **@additional_details.slice(token_field)) - end - end - - def track_event - audit_context = { - name: @name, - author: @author, - scope: @scope, - target: @runner, - target_details: runner_path, - additional_details: @additional_details.presence, - message: safe_format(@message, runner_type: runner_type, **@kwargs) - }.compact - - ::Gitlab::Audit::Auditor.audit(audit_context) - end - - private - - def runner_type - @runner.runner_type.chomp('_type') - end - - def runner_path - url_helpers = ::Gitlab::Routing.url_helpers - - if @runner.group_type? - url_helpers.group_runner_path(@scope, @runner) - elsif @runner.project_type? - url_helpers.project_runner_path(@scope, @runner) - else - url_helpers.admin_runner_path(@runner) - end - end - end -end diff --git a/ee/app/services/audit_events/safe_runner_token.rb b/ee/app/services/audit_events/safe_runner_token.rb deleted file mode 100644 index 483f105d37e247..00000000000000 --- a/ee/app/services/audit_events/safe_runner_token.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -module AuditEvents - module SafeRunnerToken - SAFE_TOKEN_LENGTH = 8 - - def safe_author(author) - return author unless author.is_a?(String) - - safe_token_length = SAFE_TOKEN_LENGTH - if author.start_with?(::RunnersTokenPrefixable::RUNNERS_TOKEN_PREFIX) - safe_token_length += ::RunnersTokenPrefixable::RUNNERS_TOKEN_PREFIX.length - end - - author[0...safe_token_length] - end - end -end 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 deleted file mode 100644 index 01914c8d9e51fe..00000000000000 --- a/ee/app/services/audit_events/streaming/event_type_filters/base_service.rb +++ /dev/null @@ -1,56 +0,0 @@ -# frozen_string_literal: true - -module AuditEvents - module Streaming - module EventTypeFilters - class BaseService - include ::AuditEvents::EventFilterSyncHelper - - attr_reader :destination, :event_type_filters, :current_user, :model - - def initialize(destination:, event_type_filters:, current_user:) - @destination = destination - @event_type_filters = event_type_filters - @current_user = current_user - @model = model_of_destination(destination) - end - - private - - def model_of_destination(destination) - case destination - when AuditEvents::InstanceExternalAuditEventDestination - ::AuditEvents::Streaming::InstanceEventTypeFilter - when AuditEvents::Group::ExternalStreamingDestination - ::AuditEvents::Group::EventTypeFilter - when AuditEvents::Instance::ExternalStreamingDestination - ::AuditEvents::Instance::EventTypeFilter - else - ::AuditEvents::Streaming::EventTypeFilter - end - end - - def log_audit_event(name:, message:) - audit_context = { - name: name, - author: current_user, - scope: get_audit_scope, - target: destination, - message: "#{message}: #{event_type_filters.to_sentence}" - } - - ::Gitlab::Audit::Auditor.audit(audit_context) - end - - def get_audit_scope - if destination.is_a?(AuditEvents::InstanceExternalAuditEventDestination) || - destination.is_a?(AuditEvents::Instance::ExternalStreamingDestination) - Gitlab::Audit::InstanceScope.new - else - destination.group - end - 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 deleted file mode 100644 index a679d5fe6f0677..00000000000000 --- a/ee/app/services/audit_events/streaming/event_type_filters/create_service.rb +++ /dev/null @@ -1,70 +0,0 @@ -# frozen_string_literal: true - -module AuditEvents - module Streaming - module EventTypeFilters - class CreateService < BaseService - def execute - begin - create_event_type_filters! - log_audit_event(name: 'event_type_filters_created', message: 'Created audit event type filter(s)') - - sync_filters - rescue ActiveRecord::RecordInvalid => e - return ServiceResponse.error(message: e.message) - end - - ServiceResponse.success - end - - private - - def create_event_type_filters! - model.transaction do - created = [] - - event_type_filters.each do |filter| - filter_model = destination.event_type_filters.create!(audit_event_type: filter) - created << filter_model - end - - created - end - end - - def sync_filters - case destination - when AuditEvents::ExternalAuditEventDestination, AuditEvents::InstanceExternalAuditEventDestination - sync_to_streaming_destination if should_sync_to_streaming? - when AuditEvents::Group::ExternalStreamingDestination, AuditEvents::Instance::ExternalStreamingDestination - sync_to_legacy_destination if should_sync_to_legacy? - end - end - - def should_sync_to_streaming? - is_instance = destination.is_a?(AuditEvents::InstanceExternalAuditEventDestination) - - destination.stream_destination.present? && - legacy_destination_sync_enabled?(destination, is_instance) - end - - def should_sync_to_legacy? - destination.legacy_destination_ref.present? && - stream_destination_sync_enabled?(destination) - end - - def sync_to_streaming_destination - event_type_filters.each do |filter| - sync_stream_event_type_filter(destination, filter) - end - end - - def sync_to_legacy_destination - event_type_filters.each do |filter| - sync_legacy_event_type_filter(destination, filter) - end - end - end - end - end -end diff --git a/ee/app/services/audit_events/streaming/event_type_filters/destroy_service.rb b/ee/app/services/audit_events/streaming/event_type_filters/destroy_service.rb deleted file mode 100644 index baa943bdf00984..00000000000000 --- a/ee/app/services/audit_events/streaming/event_type_filters/destroy_service.rb +++ /dev/null @@ -1,66 +0,0 @@ -# frozen_string_literal: true - -module AuditEvents - module Streaming - module EventTypeFilters - class DestroyService < BaseService - def execute - errors = validate! - - if errors.blank? - filters_to_delete = destination.event_type_filters - .audit_event_type_in(event_type_filters) - .pluck_audit_event_type - - destination.event_type_filters.audit_event_type_in(event_type_filters).delete_all - - sync_deletions(filters_to_delete) - - log_audit_event(name: 'event_type_filters_deleted', message: 'Deleted audit event type filter(s)') - ServiceResponse.success - else - ServiceResponse.error(message: errors) - end - end - - private - - def validate! - existing_filters = destination.event_type_filters - .audit_event_type_in(event_type_filters) - .pluck_audit_event_type - missing_filters = event_type_filters - existing_filters - [error_message(missing_filters)] if missing_filters.present? - end - - def error_message(missing_filters) - format(_("Couldn't find event type filters where audit event type(s): %{missing_filters}"), - missing_filters: missing_filters.join(', ')) - end - - def sync_deletions(filters_to_delete) - return if filters_to_delete.blank? - - case destination - when AuditEvents::ExternalAuditEventDestination, AuditEvents::InstanceExternalAuditEventDestination - sync_delete_stream_event_type_filter(destination, filters_to_delete) if should_sync_to_streaming? - when AuditEvents::Group::ExternalStreamingDestination, AuditEvents::Instance::ExternalStreamingDestination - sync_delete_legacy_event_type_filter(destination, filters_to_delete) if should_sync_to_legacy? - end - end - - def should_sync_to_streaming? - is_instance = destination.instance_level? - - destination.stream_destination_id.present? && - legacy_destination_sync_enabled?(destination, is_instance) - end - - def should_sync_to_legacy? - destination.legacy_destination_ref.present? && - stream_destination_sync_enabled?(destination) - end - end - end - end -end diff --git a/ee/app/services/audit_events/streaming/headers/base.rb b/ee/app/services/audit_events/streaming/headers/base.rb deleted file mode 100644 index 5e145e5196aebd..00000000000000 --- a/ee/app/services/audit_events/streaming/headers/base.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true -module AuditEvents - module Streaming - module Headers - class Base < ::BaseGroupService - include AuditEvents::Streaming::HeadersOperations - - attr_reader :destination - - def initialize(destination:, current_user: nil, params: {}) - @destination = destination - - super( - group: @destination&.group, - current_user: current_user, - params: params - ) - end - - def execute - return destination_error if destination.blank? - end - - private - - def destination_error - ServiceResponse.error(message: "missing destination param") - end - - def audit(action:, header:, message:, author: current_user, additional_details: {}) - audit_context = { - name: "audit_events_streaming_headers_#{action}", - author: author, - scope: group, - target: header, - target_details: header.key, - message: message, - stream_only: false, - additional_details: additional_details - } - - ::Gitlab::Audit::Auditor.audit(audit_context) - end - end - end - end -end diff --git a/ee/app/services/audit_events/streaming/headers/create_service.rb b/ee/app/services/audit_events/streaming/headers/create_service.rb deleted file mode 100644 index d20ebada8f771c..00000000000000 --- a/ee/app/services/audit_events/streaming/headers/create_service.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -module AuditEvents - module Streaming - module Headers - class CreateService < Base - def execute - super - create_header(destination, params[:key], params[:value], params[:active]) - end - end - end - end -end diff --git a/ee/app/services/audit_events/streaming/headers/destroy_service.rb b/ee/app/services/audit_events/streaming/headers/destroy_service.rb deleted file mode 100644 index 7301a5aa25bc0b..00000000000000 --- a/ee/app/services/audit_events/streaming/headers/destroy_service.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true -module AuditEvents - module Streaming - module Headers - class DestroyService < Base - def execute - super - - header = params[:header] - return header_error if header.blank? - - destroy_header(params[:header]) - end - - private - - def header_error - ServiceResponse.error(message: "missing header param") - end - end - end - end -end diff --git a/ee/app/services/audit_events/streaming/headers/update_service.rb b/ee/app/services/audit_events/streaming/headers/update_service.rb deleted file mode 100644 index 9dc64be722eec6..00000000000000 --- a/ee/app/services/audit_events/streaming/headers/update_service.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true -module AuditEvents - module Streaming - module Headers - class UpdateService < Base - def execute - super - - header = params[:header] - return header_error if header.blank? - - update_header(header, params) - end - - private - - def header_error - ServiceResponse.error(message: "missing header param") - end - end - end - end -end diff --git a/ee/app/services/audit_events/streaming/headers_operations.rb b/ee/app/services/audit_events/streaming/headers_operations.rb deleted file mode 100644 index c2cbe016d5bfe1..00000000000000 --- a/ee/app/services/audit_events/streaming/headers_operations.rb +++ /dev/null @@ -1,81 +0,0 @@ -# frozen_string_literal: true - -module AuditEvents - module Streaming - module HeadersOperations - include ::AuditEvents::HeadersSyncHelper - - def create_header(destination, key, value, active) - header = destination.headers.new(key: key, value: value, active: active) - - if header.save - audit_message = "Created custom HTTP header with key #{key}." - audit(action: :create, header: header, message: audit_message) - - sync_header_to_streaming_destination(destination, header) - - ServiceResponse.success(payload: { header: header, errors: [] }) - else - ServiceResponse.error(message: Array(header.errors)) - end - end - - def update_header(header, params) - update_params = params.slice(:key, :value, :active).compact - old_key = header.key - destination = header.destination - - if header.update(update_params) - log_update_audit_event(header) - - sync_header_to_streaming_destination(destination, header, old_key) - - ServiceResponse.success(payload: { header: header, errors: [] }) - else - ServiceResponse.error(message: Array(header.errors)) - end - end - - def destroy_header(header) - destination = header.destination - - if header.destroy - audit_message = "Destroyed a custom HTTP header with key #{header.key}." - audit(action: :destroy, header: header, message: audit_message) - - sync_header_deletion_to_streaming_destination(destination, header.key) - - ServiceResponse.success - else - ServiceResponse.error(message: Array(header.errors)) - end - end - - private - - def log_update_audit_event(header) - changes = header.previous_changes.except(:updated_at) - return if changes.empty? - - audit(action: :update, header: header, message: update_audit_message(header, changes), - additional_details: changes) - end - - def update_audit_message(header, changes) - message = "Updated a custom HTTP header " - - message += if changes.key?(:key) - "from key #{changes[:key].first} to have a key #{changes[:key].last}" - else - "with key #{header.key}" - end - - message += " to have a new value" if changes.key?(:value) - message += " activation status changed to #{changes[:active].last}" if changes.key?(:active) - message += "." - - message - end - end - end -end diff --git a/ee/app/services/audit_events/streaming/instance_headers/base_service.rb b/ee/app/services/audit_events/streaming/instance_headers/base_service.rb deleted file mode 100644 index 67887f9e7bcfc8..00000000000000 --- a/ee/app/services/audit_events/streaming/instance_headers/base_service.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -module AuditEvents - module Streaming - module InstanceHeaders - class BaseService - include AuditEvents::Streaming::HeadersOperations - - attr_reader :params, :current_user - - def initialize(params: {}, current_user: nil) - @params = params - @current_user = current_user - end - - def audit(action:, header:, message:, author: current_user, additional_details: {}) - audit_context = { - name: "audit_events_streaming_instance_headers_#{action}", - author: author, - scope: Gitlab::Audit::InstanceScope.new, - target: header, - target_details: header.key, - message: message, - additional_details: additional_details - } - - ::Gitlab::Audit::Auditor.audit(audit_context) - end - end - end - end -end diff --git a/ee/app/services/audit_events/streaming/instance_headers/create_service.rb b/ee/app/services/audit_events/streaming/instance_headers/create_service.rb deleted file mode 100644 index 354e2ef023d255..00000000000000 --- a/ee/app/services/audit_events/streaming/instance_headers/create_service.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module AuditEvents - module Streaming - module InstanceHeaders - class CreateService < BaseService - def execute - create_header(params[:destination], params[:key], params[:value], params[:active]) - end - end - end - end -end diff --git a/ee/app/services/audit_events/streaming/instance_headers/destroy_service.rb b/ee/app/services/audit_events/streaming/instance_headers/destroy_service.rb deleted file mode 100644 index 960971879c4124..00000000000000 --- a/ee/app/services/audit_events/streaming/instance_headers/destroy_service.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module AuditEvents - module Streaming - module InstanceHeaders - class DestroyService < BaseService - def execute - destroy_header(params[:header]) - end - end - end - end -end diff --git a/ee/app/services/audit_events/streaming/instance_headers/update_service.rb b/ee/app/services/audit_events/streaming/instance_headers/update_service.rb deleted file mode 100644 index d4e3b87681349a..00000000000000 --- a/ee/app/services/audit_events/streaming/instance_headers/update_service.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module AuditEvents - module Streaming - module InstanceHeaders - class UpdateService < BaseService - def execute - update_header(params[:header], params) - end - end - end - end -end diff --git a/ee/app/services/audit_events/user_impersonation_group_audit_event_service.rb b/ee/app/services/audit_events/user_impersonation_group_audit_event_service.rb deleted file mode 100644 index 2ac2e66b12a626..00000000000000 --- a/ee/app/services/audit_events/user_impersonation_group_audit_event_service.rb +++ /dev/null @@ -1,48 +0,0 @@ -# frozen_string_literal: true - -# Creates audit events at both the instance level -# and for all of a user's groups when the user is impersonated. -module AuditEvents - class UserImpersonationGroupAuditEventService - def initialize(impersonator:, user:, remote_ip:, created_at:, action: :started) - @impersonator = impersonator - @user = user - @remote_ip = remote_ip - @action = action.to_s - @created_at = created_at - end - - def execute - log_instance_audit_event - log_groups_audit_events - end - - def log_instance_audit_event - ::Gitlab::Audit::Auditor.audit({ - name: "user_impersonation", - author: @impersonator, - scope: @impersonator, - target: @user, - message: "#{@action.capitalize} Impersonation", - created_at: @created_at - }) - end - - def log_groups_audit_events - # Limited to 20 groups because we can't batch insert audit events - # https://gitlab.com/gitlab-org/gitlab/-/issues/352483 - @user.groups.first(20).each do |group| - audit_context = { - name: "user_impersonation", - author: @impersonator, - scope: group, - target: @user, - message: "Instance administrator #{@action} impersonation of #{@user.username}", - created_at: @created_at - } - - ::Gitlab::Audit::Auditor.audit(audit_context) - end - end - end -end diff --git a/ee/spec/services/audit_events/build_service_spec.rb b/ee/spec/services/audit_events/build_service_spec.rb deleted file mode 100644 index 67482f5392bf6d..00000000000000 --- a/ee/spec/services/audit_events/build_service_spec.rb +++ /dev/null @@ -1,217 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe AuditEvents::BuildService do - let(:author) { build_stubbed(:author, current_sign_in_ip: '127.0.0.1') } - let(:deploy_token) { build_stubbed(:deploy_token, user: author) } - let(:scope) { build_stubbed(:group) } - let(:target) { build_stubbed(:project) } - let(:ip_address) { '192.168.8.8' } - let(:message) { 'Added an interesting field from project Gotham' } - let(:additional_details) { { action: :custom } } - - subject(:service) do - described_class.new( - author: author, - scope: scope, - target: target, - message: message, - additional_details: additional_details, - ip_address: ip_address - ) - end - - describe '#execute', :request_store do - subject(:event) { service.execute } - - before do - allow(Gitlab::RequestContext.instance).to receive(:client_ip).and_return(ip_address) - end - - context 'when licensed' do - before do - stub_licensed_features(admin_audit_log: true) - end - - it 'sets correct attributes', :aggregate_failures do - freeze_time do - expect(event).to have_attributes( - author_id: author.id, - author_name: author.name, - entity_id: scope.id, - entity_type: scope.class.name) - - expect(event.details).to eq( - author_name: author.name, - author_class: author.class.name, - target_id: target.id, - target_type: target.class.name, - target_details: target.name, - custom_message: message, - ip_address: ip_address, - entity_path: scope.full_path, - action: :custom) - - expect(event.ip_address).to eq(ip_address) - expect(event.created_at).to eq(DateTime.current) - end - end - - context 'when IP address is not provided' do - let(:ip_address) { nil } - - it 'uses author current_sign_in_ip' do - expect(event.ip_address).to eq(author.current_sign_in_ip) - end - end - - context 'when author is impersonated' do - let(:impersonator) { build_stubbed(:user, name: 'Agent Donald', current_sign_in_ip: '8.8.8.8') } - let(:author) { build_stubbed(:author, impersonator: impersonator) } - - it 'sets author to impersonated user', :aggregate_failures do - expect(event.author_id).to eq(author.id) - expect(event.author_name).to eq(author.name) - end - - it 'includes impersonator name in message' do - expect(event.details[:custom_message]) - .to eq('Added an interesting field from project Gotham (by Agent Donald)') - end - - context 'when IP address is not provided' do - let(:ip_address) { nil } - - it 'uses impersonator current_sign_in_ip' do - expect(event.ip_address).to eq(impersonator.current_sign_in_ip) - end - end - end - - context 'when overriding target details' do - subject(:service) do - described_class.new( - author: author, - scope: scope, - target: target, - message: message, - target_details: "This is my target details" - ) - end - - it 'uses correct target details' do - expect(event.target_details).to eq("This is my target details") - end - end - - context 'when deploy token is passed as author' do - let(:service) do - described_class.new( - author: deploy_token, - scope: scope, - target: target, - message: message - ) - end - - it 'expect author to be user' do - expect(event.author_id).to eq(-2) - expect(event.author_name).to eq(deploy_token.name) - end - end - - context 'when deploy key is passed as author' do - let(:deploy_key) { build_stubbed(:deploy_key, user: author) } - - let(:service) do - described_class.new( - author: deploy_key, - scope: scope, - target: target, - message: message - ) - end - - it 'expect author to be deploy key' do - expect(event.author_id).to eq(-3) - expect(event.author_name).to eq(deploy_key.name) - end - end - - context 'when author is passed as UnauthenticatedAuthor' do - let(:service) do - described_class.new( - author: ::Gitlab::Audit::UnauthenticatedAuthor.new, - scope: scope, - target: target, - message: message - ) - end - - it 'sets author as unauthenticated user' do - expect(event.author).to be_an_instance_of(::Gitlab::Audit::UnauthenticatedAuthor) - expect(event.author_name).to eq('An unauthenticated user') - end - end - - context 'when author is a CiRunnerTokenAuthor' do - let(:service) do - described_class.new( - author: ::Gitlab::Audit::CiRunnerTokenAuthor.new( - entity_type: 'Group', - entity_path: 'a/b', - runner_authentication_token: 'token' - ), - scope: scope, - target: target, - message: message - ) - end - - it 'sets author as unauthenticated user' do - expect(event.author).to be_an_instance_of(::Gitlab::Audit::UnauthenticatedAuthor) - expect(event.author_name).to eq('Authentication token: token') - end - end - end - - context 'when not licensed' do - before do - stub_licensed_features(admin_audit_log: false) - end - - it 'sets correct attributes', :aggregate_failures do - freeze_time do - expect(event).to have_attributes( - author_id: author.id, - author_name: author.name, - entity_id: scope.id, - entity_type: scope.class.name) - - expect(event.details).to eq( - author_name: author.name, - author_class: author.class.name, - target_id: target.id, - target_type: target.class.name, - target_details: target.name, - custom_message: message, - action: :custom) - - expect(event.ip_address).to be_nil - expect(event.created_at).to eq(DateTime.current) - end - end - - context 'when author is impersonated' do - let(:impersonator) { build_stubbed(:user, name: 'Agent Donald', current_sign_in_ip: '8.8.8.8') } - let(:author) { build_stubbed(:author, impersonator: impersonator) } - - it 'does not includes impersonator name in message' do - expect(event.details[:custom_message]) - .to eq('Added an interesting field from project Gotham') - end - end - end - end -end diff --git a/ee/spec/services/audit_events/compliance_violation_scheduler_spec.rb b/ee/spec/services/audit_events/compliance_violation_scheduler_spec.rb deleted file mode 100644 index 292378baaa643a..00000000000000 --- a/ee/spec/services/audit_events/compliance_violation_scheduler_spec.rb +++ /dev/null @@ -1,278 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe AuditEvents::ComplianceViolationScheduler, feature_category: :compliance_management do - let(:scheduler) { described_class.new(audit_events) } - - describe '#execute' do - context 'with multiple audit events' do - let(:audit_event_1) { create(:audit_events_project_audit_event) } - let(:audit_event_2) { create(:audit_events_project_audit_event) } - let(:audit_events) { [audit_event_1, audit_event_2] } - - it 'schedules compliance check for each audit event' do - expect(scheduler).to receive(:schedule_compliance_check).with(audit_event_1) - expect(scheduler).to receive(:schedule_compliance_check).with(audit_event_2) - - scheduler.execute - end - end - - context 'with empty audit events' do - let(:audit_events) { [] } - - it 'does not schedule any compliance checks' do - expect(scheduler).not_to receive(:schedule_compliance_check) - - scheduler.execute - end - end - end - - describe '#schedule_compliance_check' do - let(:audit_events) { [audit_event] } - - context 'when should_schedule_compliance_check? returns false' do - let(:audit_event) { create(:audit_event) } - - before do - allow(scheduler).to receive(:should_schedule_compliance_check?).with(audit_event).and_return(false) - end - - it 'does not schedule worker' do - expect(::ComplianceManagement::ComplianceViolationDetectionWorker).not_to receive(:perform_async) - - scheduler.send(:schedule_compliance_check, audit_event) - end - end - - context 'when should_schedule_compliance_check? returns true' do - let(:audit_event) { create(:audit_events_project_audit_event, event_name: 'test_event') } - - before do - allow(scheduler).to receive(:should_schedule_compliance_check?).with(audit_event).and_return(true) - end - - context 'when event definition exists but has no compliance controls' do - let(:event_definition) { instance_double(Gitlab::Audit::Type::Definition, compliance_controls: []) } - - before do - allow(Gitlab::Audit::Type::Definition).to receive(:get).with('test_event').and_return(event_definition) - end - - it 'does not schedule worker' do - expect(::ComplianceManagement::ComplianceViolationDetectionWorker).not_to receive(:perform_async) - - scheduler.send(:schedule_compliance_check, audit_event) - end - end - - context 'when event definition exists and has compliance controls' do - let(:event_definition) { instance_double(Gitlab::Audit::Type::Definition, compliance_controls: ['control1']) } - - before do - allow(Gitlab::Audit::Type::Definition).to receive(:get).with('test_event').and_return(event_definition) - end - - it 'schedules worker with correct parameters' do - expected_params = { - 'audit_event_id' => audit_event.id, - 'audit_event_class_name' => audit_event.class.name - } - - expect(::ComplianceManagement::ComplianceViolationDetectionWorker) - .to receive(:perform_async).with(expected_params) - - scheduler.send(:schedule_compliance_check, audit_event) - end - end - end - end - - describe '#should_schedule_compliance_check?' do - let(:audit_events) { [audit_event] } - - context 'when feature flag is enabled' do - before do - stub_feature_flags(enable_project_compliance_violations: true) - end - - context 'when audit event has no entity' do - let(:audit_event) { create(:audit_events_project_audit_event, project_id: non_existing_record_id) } - - before do - allow(audit_event).to receive(:entity).and_return(nil) - end - - it 'returns false' do - expect(scheduler.send(:should_schedule_compliance_check?, audit_event)).to be_falsey - end - end - - context 'when audit event entity is NullEntity' do - let(:audit_event) { create(:audit_events_project_audit_event, project_id: non_existing_record_id) } - - before do - allow(audit_event).to receive(:entity).and_return(Gitlab::Audit::NullEntity.new) - end - - it 'returns false' do - expect(scheduler.send(:should_schedule_compliance_check?, audit_event)).to be_falsey - end - end - - context 'when audit event has no event_name' do - let_it_be(:project) { create(:project) } - let(:audit_event) { create(:audit_events_project_audit_event, project_id: project.id, event_name: nil) } - - before do - allow(audit_event).to receive(:project).and_return(project) - allow(project).to receive(:licensed_feature_available?) - .with(:project_level_compliance_violations_report).and_return(true) - end - - it 'returns false and logs the missing event_name' do - expect(Gitlab::AppLogger).to receive(:info).with( - message: "Audit event without event_name encountered in compliance scheduler", - audit_event_id: audit_event.id, - audit_event_class: audit_event.class.name - ) - - expect(scheduler.send(:should_schedule_compliance_check?, audit_event)).to be false - end - end - - context 'when audit event has blank event_name' do - let_it_be(:project) { create(:project) } - let(:audit_event) { create(:audit_events_project_audit_event, project_id: project.id, event_name: '') } - - before do - allow(audit_event).to receive(:project).and_return(project) - allow(project).to receive(:licensed_feature_available?) - .with(:project_level_compliance_violations_report).and_return(true) - end - - it 'returns false and logs the missing event_name' do - expect(Gitlab::AppLogger).to receive(:info).with( - message: "Audit event without event_name encountered in compliance scheduler", - audit_event_id: audit_event.id, - audit_event_class: audit_event.class.name - ) - - expect(scheduler.send(:should_schedule_compliance_check?, audit_event)).to be false - end - end - - context 'when audit event has whitespace-only event_name' do - let_it_be(:project) { create(:project) } - let(:audit_event) { create(:audit_events_project_audit_event, project_id: project.id, event_name: ' ') } - - before do - allow(audit_event).to receive(:project).and_return(project) - allow(project).to receive(:licensed_feature_available?) - .with(:project_level_compliance_violations_report).and_return(true) - end - - it 'returns false and logs the missing event_name' do - expect(Gitlab::AppLogger).to receive(:info).with( - message: "Audit event without event_name encountered in compliance scheduler", - audit_event_id: audit_event.id, - audit_event_class: audit_event.class.name - ) - - expect(scheduler.send(:should_schedule_compliance_check?, audit_event)).to be false - end - end - - context 'when audit event entity is a Project' do - let_it_be(:project) { create(:project) } - let(:audit_event) do - create(:audit_events_project_audit_event, project_id: project.id, event_name: 'test_event') - end - - context 'when project has licensed feature available' do - before do - allow(audit_event).to receive(:project).and_return(project) - allow(project).to receive(:licensed_feature_available?) - .with(:project_level_compliance_violations_report).and_return(true) - end - - it 'returns true' do - expect(scheduler.send(:should_schedule_compliance_check?, audit_event)).to be true - end - end - - context 'when project does not have licensed feature available' do - before do - allow(audit_event).to receive(:project).and_return(project) - allow(project).to receive(:licensed_feature_available?) - .with(:project_level_compliance_violations_report).and_return(false) - end - - it 'returns false' do - expect(scheduler.send(:should_schedule_compliance_check?, audit_event)).to be false - end - end - end - - context 'when audit event entity is a Group' do - let(:group) { create(:group) } - let(:audit_event) { create(:audit_events_group_audit_event, group_id: group.id, event_name: 'test_event') } - - context 'when group has licensed feature available' do - before do - allow(audit_event).to receive(:group).and_return(group) - allow(group).to receive(:licensed_feature_available?) - .with(:group_level_compliance_violations_report).and_return(true) - end - - it 'returns true' do - expect(scheduler.send(:should_schedule_compliance_check?, audit_event)).to be true - end - end - - context 'when group does not have licensed feature available' do - before do - allow(audit_event).to receive(:group).and_return(group) - allow(group).to receive(:licensed_feature_available?) - .with(:group_level_compliance_violations_report).and_return(false) - end - - it 'returns false' do - expect(scheduler.send(:should_schedule_compliance_check?, audit_event)).to be false - end - end - end - - context 'when audit event entity is neither Project nor Group' do - let(:audit_event) { create(:audit_events_user_audit_event, event_name: 'test_event') } - - it 'returns nil' do - expect(scheduler.send(:should_schedule_compliance_check?, audit_event)).to be_nil - end - end - end - - context 'when feature flag is disabled' do - let_it_be(:project) { create(:project) } - let(:audit_event) { create(:audit_events_project_audit_event, project_id: project.id, event_name: 'test_event') } - - before do - stub_feature_flags(enable_project_compliance_violations: false) - end - - context 'when project has licensed feature available' do - before do - allow(audit_event).to receive(:project).and_return(project) - allow(project).to receive(:licensed_feature_available?) - .with(:project_level_compliance_violations_report).and_return(true) - end - - it 'returns false' do - expect(scheduler.send(:should_schedule_compliance_check?, audit_event)).to be false - end - end - end - end -end diff --git a/ee/spec/services/audit_events/export_csv_service_spec.rb b/ee/spec/services/audit_events/export_csv_service_spec.rb deleted file mode 100644 index d0326e0858364f..00000000000000 --- a/ee/spec/services/audit_events/export_csv_service_spec.rb +++ /dev/null @@ -1,136 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe AuditEvents::ExportCsvService, feature_category: :audit_events do - let_it_be(:author) { create(:user, name: "Ru'by McRüb\"Face") } - let_it_be(:audit_event) do - create(:project_audit_event, - entity_id: 678, - entity_type: 'Project', - entity_path: 'gitlab-org/awesome-rails', - target_details: "special package ¯\\_(ツ)_/¯", - author_id: author.id, - ip_address: IPAddr.new('192.168.0.1'), - details: { - custom_message: "Removed package ,./;'[]\-=", - target_id: 3, target_type: 'Package' - }, - created_at: Time.zone.parse('2020-02-20T12:00:00Z')) - end - - let(:params) do - { - entity_type: 'Project', - entity_id: 678, - created_before: '2020-03-01', - created_after: '2020-01-01', - author_id: author.id - } - end - - let(:export_csv_service) { described_class.new(params) } - - subject(:csv) { CSV.parse(export_csv_service.csv_data.to_a.join, headers: true) } - - it 'includes the appropriate headers' do - expect(csv.headers).to eq([ - 'ID', 'Author ID', 'Author Name', 'Author Email', - 'Entity ID', 'Entity Type', 'Entity Path', - 'Target ID', 'Target Type', 'Target Details', - 'Action', 'IP Address', 'Created At (UTC)' - ]) - end - - context 'data verification' do - specify 'ID' do - expect(csv[0]['ID']).to eq(audit_event.id.to_s) - end - - specify 'Author ID' do - expect(csv[0]['Author ID']).to eq(author.id.to_s) - end - - specify 'Author Name' do - expect(csv[0]['Author Name']).to eq("Ru'by McRüb\"Face") - end - - specify 'Author Email' do - expect(csv[0]['Author Email']).to eq(author.email) - end - - specify 'Entity ID' do - expect(csv[0]['Entity ID']).to eq('678') - end - - specify 'Entity Type' do - expect(csv[0]['Entity Type']).to eq('Project') - end - - specify 'Entity Path' do - expect(csv[0]['Entity Path']).to eq('gitlab-org/awesome-rails') - end - - specify 'Target ID' do - expect(csv[0]['Target ID']).to eq('3') - end - - specify 'Target Type' do - expect(csv[0]['Target Type']).to eq('Package') - end - - specify 'Target Details' do - expect(csv[0]['Target Details']).to eq("special package ¯\\_(ツ)_/¯") - end - - specify 'Action' do - expect(csv[0]['Action']).to eq("Removed package ,./;'[]\-=") - end - - specify 'IP Address' do - expect(csv[0]['IP Address']).to eq('192.168.0.1') - end - - specify 'Created At (UTC)' do - expect(csv[0]['Created At (UTC)']).to eq('2020-02-20T12:00:00Z') - end - end - - context 'when the author user has been deleted' do - subject(:csv) { CSV.parse(export_csv_service.csv_data.to_a.join, headers: true) } - - let(:params) { super().tap { |params| params.delete(:author_id) } } - - before do - user = create(:user, name: "foo") - create(:project_audit_event, - entity_id: 678, - entity_type: 'Project', - author_id: user.id, - created_at: Time.zone.parse('2020-02-20T12:00:00Z')) - - user.delete - end - - it "returns CSV without error" do - expect { csv.headers }.not_to raise_error - end - end - - context 'with preloads' do - let(:params) { super().tap { |params| params.delete(:author_id) } } - - it 'preloads fields to avoid N+1 queries' do - described_class.new(params).csv_data.to_a # warm-up - control = ActiveRecord::QueryRecorder.new { described_class.new(params).csv_data.to_a } - - create(:project_audit_event, - entity_id: 678, - entity_type: 'Project', - author_id: create(:user).id, - created_at: Time.zone.parse('2020-02-20T12:00:00Z')) - - expect { described_class.new(params).csv_data.to_a }.not_to exceed_query_limit(control) - end - end -end diff --git a/ee/spec/services/audit_events/protected_branch_audit_event_service_spec.rb b/ee/spec/services/audit_events/protected_branch_audit_event_service_spec.rb deleted file mode 100644 index 750f48c30e9ae8..00000000000000 --- a/ee/spec/services/audit_events/protected_branch_audit_event_service_spec.rb +++ /dev/null @@ -1,106 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe AuditEvents::ProtectedBranchAuditEventService, :request_store, feature_category: :audit_events do - let(:merge_level) { 'Maintainers' } - let(:push_level) { 'No one' } - let_it_be(:author) { create(:user, :with_sign_ins) } - let_it_be(:group) { create(:group) } - let_it_be(:destination) { create(:external_audit_event_destination, group: group) } - let_it_be(:entity) { create(:project, creator: author, group: group) } - let_it_be(:protected_branch) { create(:protected_branch, :no_one_can_push, allow_force_push: true, code_owner_approval_required: true, project: entity) } - - let(:logger) { instance_spy(Gitlab::AuditJsonLogger) } - let(:ip_address) { '192.168.15.18' } - - describe '#security_event' do - shared_examples 'loggable' do |action, event_type| - context "when a protected_branch is #{action}" do - let(:service) { described_class.new(author, protected_branch, action) } - - before do - stub_licensed_features(admin_audit_log: true, external_audit_events: true, code_owner_approval_required: true) - allow(Gitlab::RequestContext.instance).to receive(:client_ip).and_return(ip_address) - end - - it 'creates an event', :aggregate_failures do - expect { service.execute }.to change(AuditEvent, :count).by(1) - - security_event = AuditEvent.last - - expect(security_event.details).to include( - author_name: author.name, - target_id: protected_branch.id, - entity_path: entity.full_path, - target_type: 'ProtectedBranch', - target_details: protected_branch.name, - allow_force_push: true, - code_owner_approval_required: true, - push_access_levels: [push_level], - merge_access_levels: [merge_level], - ip_address: ip_address - ) - - expect(security_event.author_id).to eq(author.id) - expect(security_event.entity_id).to eq(entity.id) - expect(security_event.entity_type).to eq('Project') - expect(security_event.ip_address).to eq(ip_address) - end - - it 'logs to a file with the provided details' do - expect(::Gitlab::AuditJsonLogger).to receive(:build).and_return(logger) - - service.execute - - expect(logger).to have_received(:info).with( - hash_including( - 'author_id' => author.id, - 'author_name' => author.name, - 'entity_id' => entity.id, - 'entity_type' => 'Project', - 'entity_path' => entity.full_path, - 'allow_force_push' => true, - 'code_owner_approval_required' => true, - 'merge_access_levels' => [merge_level], - 'push_access_levels' => [push_level], - 'target_details' => protected_branch.name, - 'target_id' => protected_branch.id, - 'target_type' => 'ProtectedBranch', - 'custom_message' => action == :add ? /Added/ : /Unprotected/, - 'ip_address' => ip_address, - 'created_at' => anything - ) - ) - end - - it_behaves_like 'sends correct event type in audit event stream' do - let(:subject) { service.execute } - - let_it_be(:event_type) { event_type } - end - end - end - - include_examples 'loggable', :add, 'protected_branch_created' - include_examples 'loggable', :remove, 'protected_branch_removed' - - context 'when not licensed' do - let(:service) { described_class.new(author, protected_branch, :add) } - - before do - stub_licensed_features( - audit_events: false, - extended_audit_events: false, - admin_audit_log: false - ) - end - - it "doesn't create an event or log to a file", :aggregate_failures do - expect(service).not_to receive(:file_logger) - - expect { service.execute }.not_to change(AuditEvent, :count) - end - end - end -end diff --git a/ee/spec/services/audit_events/release_artifacts_downloaded_audit_event_service_spec.rb b/ee/spec/services/audit_events/release_artifacts_downloaded_audit_event_service_spec.rb deleted file mode 100644 index 3a64200ec9c561..00000000000000 --- a/ee/spec/services/audit_events/release_artifacts_downloaded_audit_event_service_spec.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe AuditEvents::ReleaseArtifactsDownloadedAuditEventService do - describe '#security_event' do - include_examples 'logs the release audit event' do - let(:release) { create(:release, project: entity) } - let(:custom_message) { 'Repository External Resource Download Started' } - end - end -end diff --git a/ee/spec/services/audit_events/release_associate_milestone_audit_event_service_spec.rb b/ee/spec/services/audit_events/release_associate_milestone_audit_event_service_spec.rb deleted file mode 100644 index 1625241d4dd951..00000000000000 --- a/ee/spec/services/audit_events/release_associate_milestone_audit_event_service_spec.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe AuditEvents::ReleaseAssociateMilestoneAuditEventService do - describe '#security_event' do - context 'with no milestones' do - include_examples 'logs the release audit event' do - let(:release) { create(:release, project: entity) } - let(:custom_message) { "Milestones associated with release changed to [none]" } - end - end - - context "with one milestone" do - include_examples 'logs the release audit event' do - let(:release) { create(:release, :with_milestones, milestones_count: 1, project: entity) } - let(:custom_message) { "Milestones associated with release changed to #{Milestone.first.title}" } - end - end - - context "with multiple milestones" do - include_examples 'logs the release audit event' do - let(:release) { create(:release, :with_milestones, milestones_count: 2, project: entity) } - let(:custom_message) { "Milestones associated with release changed to #{Milestone.first.title}, #{Milestone.second.title}" } - end - end - end -end diff --git a/ee/spec/services/audit_events/release_created_audit_event_service_spec.rb b/ee/spec/services/audit_events/release_created_audit_event_service_spec.rb deleted file mode 100644 index 6747654c3a5053..00000000000000 --- a/ee/spec/services/audit_events/release_created_audit_event_service_spec.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe AuditEvents::ReleaseCreatedAuditEventService do - describe '#security_event' do - context 'with no milestones' do - include_examples 'logs the release audit event' do - let(:release) { create(:release, project: entity) } - let(:custom_message) { "Created Release #{release.tag}" } - end - end - - context "with one milestone" do - include_examples 'logs the release audit event' do - let(:release) { create(:release, :with_milestones, milestones_count: 1, project: entity) } - let(:custom_message) { "Created Release #{release.tag} with Milestone #{Milestone.first.title}" } - end - end - - context "with multiple milestones" do - include_examples 'logs the release audit event' do - let(:release) { create(:release, :with_milestones, milestones_count: 2, project: entity) } - let(:custom_message) { "Created Release #{release.tag} with Milestones #{Milestone.order_by_dates_and_title.first.title}, #{Milestone.order_by_dates_and_title.second.title}" } - end - end - end -end diff --git a/ee/spec/services/audit_events/release_updated_audit_event_service_spec.rb b/ee/spec/services/audit_events/release_updated_audit_event_service_spec.rb deleted file mode 100644 index d2f330a46b516a..00000000000000 --- a/ee/spec/services/audit_events/release_updated_audit_event_service_spec.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe AuditEvents::ReleaseUpdatedAuditEventService do - describe '#security_event' do - include_examples 'logs the release audit event' do - let(:release) { create(:release, project: entity) } - let(:custom_message) { "Updated Release #{release.tag}" } - end - end -end diff --git a/ee/spec/services/audit_events/runner_audit_event_service_spec.rb b/ee/spec/services/audit_events/runner_audit_event_service_spec.rb deleted file mode 100644 index 06b91807868ac6..00000000000000 --- a/ee/spec/services/audit_events/runner_audit_event_service_spec.rb +++ /dev/null @@ -1,145 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe AuditEvents::RunnerAuditEventService, feature_category: :runner do - let_it_be(:user) { create(:user) } - let_it_be(:owner) { create(:user) } - - let(:kwargs) { {} } - let(:expected_message) { "Created #{runner.runner_type.chomp('_type')} CI runner" } - let(:service) do - described_class.new(runner, author, scope, name: expected_attrs[:name], message: expected_message, **kwargs) - end - - let(:common_attrs) do - { - name: SecureRandom.hex(8), - author: author, - target: runner - } - end - - let(:expected_attrs) do - common_attrs.merge( - message: expected_message, - scope: expected_scope, - target_details: target_details - ) - end - - shared_examples 'expected audit event' do - it 'returns audit event attributes' do - expect(::Gitlab::Audit::Auditor).to receive(:audit).with(expected_attrs) - - track_event - end - - context 'with nil message' do - let(:expected_message) { nil } - - it 'raises an error' do - expect { track_event }.to raise_error(ArgumentError, 'Missing message') - end - end - - context 'with additional kwargs' do - let(:kwargs) { { action: 'foo' } } - let(:expected_message) do - "Unregistered #{runner.runner_type.chomp('_type')} CI runner. Last contacted #{runner.contacted_at}" - end - - it 'returns audit event attributes' do - expect(::Gitlab::Audit::Auditor).to receive(:audit).with(expected_attrs) - - track_event - end - - context 'with specified token_field' do - let(:kwargs) do - { token_field: :runner_registration_token } - end - - it 'returns audit event attributes' do - expect(::Gitlab::Audit::Auditor).to receive(:audit).with(expected_attrs) - - track_event - end - - context 'when author is a token' do - let(:author) { 'asdfs87rekjh' } - let(:safe_length) { AuditEvents::SafeRunnerToken::SAFE_TOKEN_LENGTH } - let(:safe_author) { author[0...safe_length] } - - it 'returns audit event attributes with runner token author and additional details' do - expect(::Gitlab::Audit::Auditor).to receive(:audit).with( - expected_attrs.merge( - author: an_object_having_attributes( - name: "Registration token: #{safe_author}", - entity_type: scope.class.name, - entity_path: scope.full_path), - additional_details: { runner_registration_token: safe_author }) - ) - - track_event - end - - context 'with registration token prefixed with RUNNERS_TOKEN_PREFIX' do - let(:author) { "#{::RunnersTokenPrefixable::RUNNERS_TOKEN_PREFIX}b6bce79c3a" } - let(:safe_length) do - ::RunnersTokenPrefixable::RUNNERS_TOKEN_PREFIX.length + AuditEvents::SafeRunnerToken::SAFE_TOKEN_LENGTH - end - - it 'returns audit event attributes with runner token author and additional details' do - expect(::Gitlab::Audit::Auditor).to receive(:audit).with( - expected_attrs.merge( - author: an_object_having_attributes( - name: "Registration token: #{safe_author}", - entity_type: scope.class.name, - entity_path: scope.full_path), - additional_details: { runner_registration_token: safe_author }) - ) - - track_event - end - end - end - end - end - end - - describe '#track_event' do - let(:author) { user } - let(:expected_scope) { scope } - - subject(:track_event) { service.track_event } - - context 'for instance runner' do - let_it_be(:runner) { create(:ci_runner, :online, creator_id: owner) } - let_it_be(:scope) { ::Gitlab::Audit::InstanceScope.new } - - let(:expected_scope) { an_instance_of(::Gitlab::Audit::InstanceScope) } - let(:target_details) { ::Gitlab::Routing.url_helpers.admin_runner_path(runner) } - - it_behaves_like 'expected audit event' - end - - context 'for group runner' do - let_it_be(:scope) { create(:group) } - let_it_be(:runner) { create(:ci_runner, :group, :online, groups: [scope], creator_id: owner) } - - let(:target_details) { ::Gitlab::Routing.url_helpers.group_runner_path(scope, runner) } - - it_behaves_like 'expected audit event' - end - - context 'for project runner' do - let_it_be(:scope) { create(:project) } - let_it_be(:runner) { create(:ci_runner, :project, :online, projects: [scope], creator_id: owner) } - - let(:target_details) { ::Gitlab::Routing.url_helpers.project_runner_path(scope, runner) } - - it_behaves_like 'expected audit event' - 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 deleted file mode 100644 index 8cdc762a7e0ac8..00000000000000 --- a/ee/spec/services/audit_events/streaming/event_type_filters/create_service_spec.rb +++ /dev/null @@ -1,383 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -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) { ['event_type_filters_deleted'] } - let_it_be(:user) { create(:user) } - - let(:expected_error) { [] } - - subject(:response) do - described_class.new( - destination: destination, - event_type_filters: event_type_filters, - current_user: user - ).execute - end - - describe '#execute' do - context 'when event type filter is not already present' do - shared_examples 'creates event filter' do - it 'creates event type filter', :aggregate_failures do - expect { subject }.to change { destination.event_type_filters.count }.by 1 - - expect(destination.event_type_filters.last.audit_event_type).to eq(event_type_filters.first) - 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: scope, - target: destination, - message: "Created audit event type filter(s): event_type_filters_deleted" - } - - 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 destination is group level destination' do - it_behaves_like 'creates event filter' do - let(:scope) { destination.group } - end - end - - context 'when destination is instance level destination' do - let_it_be(:destination) { create(:instance_external_audit_event_destination) } - - it_behaves_like 'creates event filter' do - let(:scope) { be_an_instance_of(Gitlab::Audit::InstanceScope) } - end - end - end - - context 'when record is invalid' do - let(:expected_error) { 'Validation Failed' } - - before do - expect_next_instance_of(::AuditEvents::Streaming::EventTypeFilter) do |filter| - allow(filter).to receive(:save!).and_raise(ActiveRecord::RecordInvalid.new(filter), expected_error) - end - end - - it 'returns error message', :aggregate_failures do - 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 - - describe 'syncing event type filters' do - let(:service_instance) do - described_class.new( - destination: destination, - event_type_filters: event_type_filters, - current_user: user - ) - end - - before do - allow(service_instance).to receive(:create_event_type_filters!) - allow(service_instance).to receive(:log_audit_event) - end - - shared_examples 'syncs to streaming destination' do - context 'when stream destination is present and sync is enabled' do - let(:stream_destination) { create(:audit_events_group_external_streaming_destination) } - - before do - allow(destination).to receive(:stream_destination).and_return(stream_destination) - allow(service_instance).to receive(:legacy_destination_sync_enabled?).and_return(true) - end - - it 'syncs each event type filter to streaming destination' do - event_type_filters.each do |filter| - expect(service_instance).to receive(:sync_stream_event_type_filter) - .with(destination, filter) - end - - service_instance.execute - end - end - - context 'when stream destination is not present' do - before do - allow(destination).to receive(:stream_destination).and_return(nil) - end - - it 'does not sync to streaming destination' do - expect(service_instance).not_to receive(:sync_stream_event_type_filter) - - service_instance.execute - end - end - - context 'when legacy destination sync is disabled' do - let(:stream_destination) { create(:audit_events_group_external_streaming_destination) } - - before do - allow(destination).to receive(:stream_destination).and_return(stream_destination) - allow(service_instance).to receive(:legacy_destination_sync_enabled?).and_return(false) - end - - it 'does not sync to streaming destination' do - expect(service_instance).not_to receive(:sync_stream_event_type_filter) - - service_instance.execute - end - end - end - - shared_examples 'syncs to legacy destination' do - context 'when legacy destination ref is present and sync is enabled' do - let(:legacy_destination) { create(:external_audit_event_destination) } - - before do - allow(destination).to receive(:legacy_destination_ref).and_return(legacy_destination) - allow(service_instance).to receive(:stream_destination_sync_enabled?).and_return(true) - end - - it 'syncs each event type filter to legacy destination' do - event_type_filters.each do |filter| - expect(service_instance).to receive(:sync_legacy_event_type_filter) - .with(destination, filter) - end - - service_instance.execute - end - end - - context 'when legacy destination ref is not present' do - before do - allow(destination).to receive(:legacy_destination_ref).and_return(nil) - end - - it 'does not sync to legacy destination' do - expect(service_instance).not_to receive(:sync_legacy_event_type_filter) - - service_instance.execute - end - end - - context 'when stream destination sync is disabled' do - let(:legacy_destination) { create(:external_audit_event_destination) } - - before do - allow(destination).to receive(:legacy_destination_ref).and_return(legacy_destination) - allow(service_instance).to receive(:stream_destination_sync_enabled?).and_return(false) - end - - it 'does not sync to legacy destination' do - expect(service_instance).not_to receive(:sync_legacy_event_type_filter) - - service_instance.execute - end - end - end - - context 'when destination is ExternalAuditEventDestination' do - let_it_be(:destination) { create(:external_audit_event_destination) } - - it_behaves_like 'syncs to streaming destination' - - it 'does not attempt to sync to legacy destination' do - expect(service_instance).not_to receive(:sync_legacy_event_type_filter) - - service_instance.execute - end - end - - context 'when destination is InstanceExternalAuditEventDestination' do - let_it_be(:destination) { create(:instance_external_audit_event_destination) } - - it_behaves_like 'syncs to streaming destination' - - it 'does not attempt to sync to legacy destination' do - expect(service_instance).not_to receive(:sync_legacy_event_type_filter) - - service_instance.execute - end - - it 'passes correct instance flag to legacy_destination_sync_enabled?' do - stream_destination = create(:audit_events_instance_external_streaming_destination) - allow(destination).to receive(:stream_destination).and_return(stream_destination) - - expect(service_instance).to receive(:legacy_destination_sync_enabled?) - .with(destination, true) - .and_return(false) - - service_instance.execute - end - end - - context 'when destination is Group::ExternalStreamingDestination' do - let(:group) { create(:group) } - let(:destination) { create(:audit_events_group_external_streaming_destination, group: group) } - - it_behaves_like 'syncs to legacy destination' - - it 'does not attempt to sync to streaming destination' do - expect(service_instance).not_to receive(:sync_stream_event_type_filter) - - service_instance.execute - end - end - - context 'when destination is Instance::ExternalStreamingDestination' do - let(:destination) { create(:audit_events_instance_external_streaming_destination) } - - it_behaves_like 'syncs to legacy destination' - - it 'does not attempt to sync to streaming destination' do - expect(service_instance).not_to receive(:sync_stream_event_type_filter) - - service_instance.execute - end - end - - context 'with multiple event type filters' do - let(:event_type_filters) { %w[repository_push_event merge_request_created user_created] } - let(:stream_destination) { create(:audit_events_group_external_streaming_destination) } - - before do - allow(destination).to receive(:stream_destination).and_return(stream_destination) - allow(service_instance).to receive(:legacy_destination_sync_enabled?).and_return(true) - end - - it 'syncs all filters in order' do - event_type_filters.each_with_index do |filter, _index| - expect(service_instance).to receive(:sync_stream_event_type_filter) - .with(destination, filter) - .ordered - end - - service_instance.execute - end - end - - context 'when sync raises an error' do - let(:stream_destination) { create(:audit_events_group_external_streaming_destination) } - - before do - allow(destination).to receive(:stream_destination).and_return(stream_destination) - allow(service_instance).to receive(:legacy_destination_sync_enabled?).and_return(true) - allow(service_instance).to receive(:sync_stream_event_type_filter) - .and_raise(StandardError, 'Sync failed') - end - - it 'does not rescue the error' do - expect { service_instance.execute }.to raise_error(StandardError, 'Sync failed') - end - end - end - end - - describe '#should_sync_to_streaming?' do - let(:service_instance) do - described_class.new( - destination: destination, - event_type_filters: event_type_filters, - current_user: user - ) - end - - context 'when destination has stream_destination' do - let(:stream_destination) { create(:audit_events_group_external_streaming_destination) } - - before do - allow(destination).to receive(:stream_destination).and_return(stream_destination) - end - - context 'when legacy destination sync is enabled' do - before do - allow(service_instance).to receive(:legacy_destination_sync_enabled?).and_return(true) - end - - it 'returns true' do - expect(service_instance.send(:should_sync_to_streaming?)).to be true - end - end - - context 'when legacy destination sync is disabled' do - before do - allow(service_instance).to receive(:legacy_destination_sync_enabled?).and_return(false) - end - - it 'returns false' do - expect(service_instance.send(:should_sync_to_streaming?)).to be false - end - end - end - - context 'when destination does not have stream_destination' do - before do - allow(destination).to receive(:stream_destination).and_return(nil) - end - - it 'returns false' do - expect(service_instance.send(:should_sync_to_streaming?)).to be false - end - end - end - - describe '#should_sync_to_legacy?' do - let(:group) { create(:group) } - let(:destination) { create(:audit_events_group_external_streaming_destination, group: group) } - let(:service_instance) do - described_class.new( - destination: destination, - event_type_filters: event_type_filters, - current_user: user - ) - end - - context 'when destination has legacy_destination_ref' do - let(:legacy_destination) { create(:external_audit_event_destination) } - - before do - allow(destination).to receive(:legacy_destination_ref).and_return(legacy_destination) - end - - context 'when stream destination sync is enabled' do - before do - allow(service_instance).to receive(:stream_destination_sync_enabled?).and_return(true) - end - - it 'returns true' do - expect(service_instance.send(:should_sync_to_legacy?)).to be true - end - end - - context 'when stream destination sync is disabled' do - before do - allow(service_instance).to receive(:stream_destination_sync_enabled?).and_return(false) - end - - it 'returns false' do - expect(service_instance.send(:should_sync_to_legacy?)).to be false - end - end - end - - context 'when destination does not have legacy_destination_ref' do - before do - allow(destination).to receive(:legacy_destination_ref).and_return(nil) - end - - it 'returns false' do - expect(service_instance.send(:should_sync_to_legacy?)).to be false - end - end - end -end diff --git a/ee/spec/services/audit_events/streaming/event_type_filters/destroy_service_spec.rb b/ee/spec/services/audit_events/streaming/event_type_filters/destroy_service_spec.rb deleted file mode 100644 index b819e830b175ee..00000000000000 --- a/ee/spec/services/audit_events/streaming/event_type_filters/destroy_service_spec.rb +++ /dev/null @@ -1,450 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe AuditEvents::Streaming::EventTypeFilters::DestroyService, feature_category: :audit_events do - let_it_be(:destination) { create(:external_audit_event_destination) } - let_it_be(:user) { create(:user) } - - before do - allow(Gitlab::Audit::Type::Definition).to receive(:defined?).and_return(true) - end - - subject(:response) do - described_class.new(destination: destination, event_type_filters: event_type_filters, current_user: user).execute - end - - describe '#execute' do - context 'when event type filter is not already present' do - let(:expected_error) { ["Couldn't find event type filters where audit event type(s): filter_2"] } - let(:event_type_filters) { ['filter_2'] } - - it 'does not delete event type filter', :aggregate_failures do - 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 - - context 'when event type filter is already present' do - shared_examples 'destroys event type filter' do - let(:expected_error) { [] } - let(:event_type_filters) { [event_type_filter.audit_event_type] } - - it 'deletes event type filter', :aggregate_failures do - expect { subject }.to change { destination.event_type_filters.count }.by(-1) - 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_deleted', - author: user, - scope: scope, - target: destination, - message: "Deleted audit event type filter(s): #{event_type_filter.audit_event_type}" - } - - 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 destination is group level destination' do - let_it_be(:event_type_filter) do - create( - :audit_events_streaming_event_type_filter, - external_audit_event_destination: destination - ) - end - - it_behaves_like 'destroys event type filter' do - let(:scope) { destination.group } - end - end - - context 'when destination is instance level destination' do - let_it_be(:destination) { create(:instance_external_audit_event_destination) } - let_it_be(:event_type_filter) do - create( - :audit_events_streaming_instance_event_type_filter, - instance_external_audit_event_destination: destination - ) - end - - it_behaves_like 'destroys event type filter' do - let(:scope) { be_an_instance_of(Gitlab::Audit::InstanceScope) } - end - end - end - - describe 'syncing event type filter deletions' do - let(:service_instance) do - described_class.new( - destination: destination, - event_type_filters: event_type_filters, - current_user: user - ) - end - - let(:event_type_filters) { %w[repository_push_event merge_request_created] } - - before do - case destination - when AuditEvents::InstanceExternalAuditEventDestination - event_type_filters.each do |filter_type| - create(:audit_events_streaming_instance_event_type_filter, - instance_external_audit_event_destination: destination, - audit_event_type: filter_type) - end - when AuditEvents::Group::ExternalStreamingDestination - event_type_filters.each do |filter_type| - create(:audit_events_group_event_type_filters, - external_streaming_destination: destination, - audit_event_type: filter_type) - end - when AuditEvents::Instance::ExternalStreamingDestination - event_type_filters.each do |filter_type| - create(:audit_events_instance_event_type_filters, - external_streaming_destination: destination, - audit_event_type: filter_type) - end - else - event_type_filters.each do |filter_type| - create(:audit_events_streaming_event_type_filter, - external_audit_event_destination: destination, - audit_event_type: filter_type) - end - end - - allow(service_instance).to receive(:log_audit_event) - end - - shared_examples 'syncs deletions to streaming destination' do - context 'when stream destination id is present and sync is enabled' do - let(:stream_destination) { create(:audit_events_group_external_streaming_destination) } - - before do - allow(destination).to receive(:stream_destination_id).and_return(stream_destination.id) - allow(service_instance).to receive(:legacy_destination_sync_enabled?).and_return(true) - end - - it 'syncs deleted filters to streaming destination' do - expect(service_instance).to receive(:sync_delete_stream_event_type_filter) do |dest, filters| - expect(dest).to eq(destination) - expect(filters).to match_array(event_type_filters) - end - - service_instance.execute - end - end - - context 'when stream destination id is not present' do - before do - allow(destination).to receive(:stream_destination_id).and_return(nil) - end - - it 'does not sync to streaming destination' do - expect(service_instance).not_to receive(:sync_delete_stream_event_type_filter) - - service_instance.execute - end - end - - context 'when legacy destination sync is disabled' do - let(:stream_destination) { create(:audit_events_group_external_streaming_destination) } - - before do - allow(destination).to receive(:stream_destination_id).and_return(stream_destination.id) - allow(service_instance).to receive(:legacy_destination_sync_enabled?).and_return(false) - end - - it 'does not sync to streaming destination' do - expect(service_instance).not_to receive(:sync_delete_stream_event_type_filter) - - service_instance.execute - end - end - - context 'when no filters are deleted' do - let(:event_type_filters) { ['non_existent_filter'] } - - it 'does not attempt to sync' do - expect(service_instance).not_to receive(:sync_delete_stream_event_type_filter) - expect(service_instance).not_to receive(:sync_delete_legacy_event_type_filter) - - service_instance.execute - end - end - end - - shared_examples 'syncs deletions to legacy destination' do - context 'when legacy destination ref is present and sync is enabled' do - let(:legacy_destination) { create(:external_audit_event_destination) } - - before do - allow(destination).to receive(:legacy_destination_ref).and_return(legacy_destination) - allow(service_instance).to receive(:stream_destination_sync_enabled?).and_return(true) - end - - it 'syncs deleted filters to legacy destination' do - expect(service_instance).to receive(:sync_delete_legacy_event_type_filter) do |dest, filters| - expect(dest).to eq(destination) - expect(filters).to match_array(event_type_filters) - end - - service_instance.execute - end - end - - context 'when legacy destination ref is not present' do - before do - allow(destination).to receive(:legacy_destination_ref).and_return(nil) - end - - it 'does not sync to legacy destination' do - expect(service_instance).not_to receive(:sync_delete_legacy_event_type_filter) - - service_instance.execute - end - end - - context 'when stream destination sync is disabled' do - let(:legacy_destination) { create(:external_audit_event_destination) } - - before do - allow(destination).to receive(:legacy_destination_ref).and_return(legacy_destination) - allow(service_instance).to receive(:stream_destination_sync_enabled?).and_return(false) - end - - it 'does not sync to legacy destination' do - expect(service_instance).not_to receive(:sync_delete_legacy_event_type_filter) - - service_instance.execute - end - end - end - - context 'when destination is ExternalAuditEventDestination' do - let_it_be(:destination) { create(:external_audit_event_destination) } - - it_behaves_like 'syncs deletions to streaming destination' - - it 'does not attempt to sync to legacy destination' do - expect(service_instance).not_to receive(:sync_delete_legacy_event_type_filter) - - service_instance.execute - end - end - - context 'when destination is InstanceExternalAuditEventDestination' do - let_it_be(:destination) { create(:instance_external_audit_event_destination) } - - it_behaves_like 'syncs deletions to streaming destination' - - it 'does not attempt to sync to legacy destination' do - expect(service_instance).not_to receive(:sync_delete_legacy_event_type_filter) - - service_instance.execute - end - - it 'passes correct instance flag to legacy_destination_sync_enabled?' do - stream_destination = create(:audit_events_instance_external_streaming_destination) - allow(destination).to receive(:stream_destination_id).and_return(stream_destination.id) - - expect(service_instance).to receive(:legacy_destination_sync_enabled?) - .with(destination, true) - .and_return(false) - - service_instance.execute - end - end - - context 'when destination is Group::ExternalStreamingDestination' do - let(:group) { create(:group) } - let(:destination) { create(:audit_events_group_external_streaming_destination, group: group) } - - it_behaves_like 'syncs deletions to legacy destination' - - it 'does not attempt to sync to streaming destination' do - expect(service_instance).not_to receive(:sync_delete_stream_event_type_filter) - - service_instance.execute - end - end - - context 'when destination is Instance::ExternalStreamingDestination' do - let(:destination) { create(:audit_events_instance_external_streaming_destination) } - - it_behaves_like 'syncs deletions to legacy destination' - - it 'does not attempt to sync to streaming destination' do - expect(service_instance).not_to receive(:sync_delete_stream_event_type_filter) - - service_instance.execute - end - end - - context 'with multiple event type filters' do - let(:event_type_filters) { %w[repository_push_event merge_request_created user_created project_deleted] } - let(:stream_destination) { create(:audit_events_group_external_streaming_destination) } - - before do - allow(destination).to receive(:stream_destination_id).and_return(stream_destination.id) - allow(service_instance).to receive(:legacy_destination_sync_enabled?).and_return(true) - end - - it 'syncs all deleted filters at once' do - expect(service_instance).to receive(:sync_delete_stream_event_type_filter) do |dest, filters| - expect(dest).to eq(destination) - expect(filters).to match_array(event_type_filters) - end.once - - service_instance.execute - end - end - - context 'when sync raises an error' do - let(:stream_destination) { create(:audit_events_group_external_streaming_destination) } - - before do - allow(destination).to receive(:stream_destination_id).and_return(stream_destination.id) - allow(service_instance).to receive(:legacy_destination_sync_enabled?).and_return(true) - allow(service_instance).to receive(:sync_delete_stream_event_type_filter) - .and_raise(StandardError, 'Sync failed') - end - - it 'does not rescue the error' do - expect { service_instance.execute }.to raise_error(StandardError, 'Sync failed') - end - end - end - end - - describe '#should_sync_to_streaming?' do - let(:service_instance) do - described_class.new( - destination: destination, - event_type_filters: ['test_filter'], - current_user: user - ) - end - - context 'when destination has stream_destination_id' do - let(:stream_destination) { create(:audit_events_group_external_streaming_destination) } - - before do - allow(destination).to receive(:stream_destination_id).and_return(stream_destination.id) - end - - context 'when legacy destination sync is enabled' do - before do - allow(service_instance).to receive(:legacy_destination_sync_enabled?).and_return(true) - end - - it 'returns true' do - expect(service_instance.send(:should_sync_to_streaming?)).to be true - end - end - - context 'when legacy destination sync is disabled' do - before do - allow(service_instance).to receive(:legacy_destination_sync_enabled?).and_return(false) - end - - it 'returns false' do - expect(service_instance.send(:should_sync_to_streaming?)).to be false - end - end - - context 'when destination is not instance level' do - before do - allow(destination).to receive(:instance_level?).and_return(false) - allow(service_instance).to receive(:legacy_destination_sync_enabled?).with(destination, - false).and_return(true) - end - - it 'passes false as instance flag' do - expect(service_instance.send(:should_sync_to_streaming?)).to be true - end - end - - context 'when destination is instance level' do - before do - allow(destination).to receive(:instance_level?).and_return(true) - allow(service_instance).to receive(:legacy_destination_sync_enabled?).with(destination, true).and_return(true) - end - - it 'passes true as instance flag' do - expect(service_instance.send(:should_sync_to_streaming?)).to be true - end - end - end - - context 'when destination does not have stream_destination_id' do - before do - allow(destination).to receive(:stream_destination_id).and_return(nil) - end - - it 'returns false' do - expect(service_instance.send(:should_sync_to_streaming?)).to be false - end - end - end - - describe '#should_sync_to_legacy?' do - let(:group) { create(:group) } - let(:destination) { create(:audit_events_group_external_streaming_destination, group: group) } - let(:service_instance) do - described_class.new( - destination: destination, - event_type_filters: ['test_filter'], - current_user: user - ) - end - - context 'when destination has legacy_destination_ref' do - let(:legacy_destination) { create(:external_audit_event_destination) } - - before do - allow(destination).to receive(:legacy_destination_ref).and_return(legacy_destination) - end - - context 'when stream destination sync is enabled' do - before do - allow(service_instance).to receive(:stream_destination_sync_enabled?).and_return(true) - end - - it 'returns true' do - expect(service_instance.send(:should_sync_to_legacy?)).to be true - end - end - - context 'when stream destination sync is disabled' do - before do - allow(service_instance).to receive(:stream_destination_sync_enabled?).and_return(false) - end - - it 'returns false' do - expect(service_instance.send(:should_sync_to_legacy?)).to be false - end - end - end - - context 'when destination does not have legacy_destination_ref' do - before do - allow(destination).to receive(:legacy_destination_ref).and_return(nil) - end - - it 'returns false' do - expect(service_instance.send(:should_sync_to_legacy?)).to be false - end - end - end -end diff --git a/ee/spec/services/audit_events/streaming/headers/base_spec.rb b/ee/spec/services/audit_events/streaming/headers/base_spec.rb deleted file mode 100644 index d8b44f1f8d1e88..00000000000000 --- a/ee/spec/services/audit_events/streaming/headers/base_spec.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe AuditEvents::Streaming::Headers::Base do - let(:header) { build_stubbed(:audit_events_streaming_header) } - let(:destination) { header.external_audit_event_destination } - - subject(:service) { described_class.new(destination: destination) } - - describe '#execute' do - subject(:response) { service.execute } - - context 'when destination is missing' do - let(:destination) { nil } - - it 'returns an error' do - expect(response).to be_error - expect(response.errors).to match_array ['missing destination param'] - end - end - end -end diff --git a/ee/spec/services/audit_events/streaming/headers/create_service_spec.rb b/ee/spec/services/audit_events/streaming/headers/create_service_spec.rb deleted file mode 100644 index 18286a779e5108..00000000000000 --- a/ee/spec/services/audit_events/streaming/headers/create_service_spec.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe AuditEvents::Streaming::Headers::CreateService, feature_category: :audit_events do - let_it_be(:user) { create(:user) } - let_it_be(:destination) { create(:external_audit_event_destination) } - let_it_be(:event_type) { "audit_events_streaming_headers_create" } - - let(:params) { {} } - - subject(:service) do - described_class.new( - current_user: user, - destination: destination, - params: params - ) - end - - describe '#execute' do - subject(:response) { service.execute } - - it_behaves_like 'header creation validation errors' - - context 'when the header is created successfully' do - let(:params) { super().merge(key: 'a_key', value: 'a_value', active: true) } - - it_behaves_like 'header creation successful' - - it 'sends the audit streaming event' do - audit_context = { - name: 'audit_events_streaming_headers_create', - stream_only: false, - author: user, - scope: destination.group, - message: "Created custom HTTP header with key a_key." - } - - expect(::Gitlab::Audit::Auditor).to receive(:audit) - .with(hash_including(audit_context)).and_call_original - expect { response }.to change { AuditEvent.count }.from(0).to(1) - end - end - end -end diff --git a/ee/spec/services/audit_events/streaming/headers/destroy_service_spec.rb b/ee/spec/services/audit_events/streaming/headers/destroy_service_spec.rb deleted file mode 100644 index 4bc1f7aaae2833..00000000000000 --- a/ee/spec/services/audit_events/streaming/headers/destroy_service_spec.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe AuditEvents::Streaming::Headers::DestroyService, feature_category: :audit_events do - let_it_be(:user) { create(:user) } - let_it_be(:header) { create(:audit_events_streaming_header) } - let_it_be(:event_type) { "audit_events_streaming_headers_destroy" } - - let(:destination) { header.external_audit_event_destination } - let(:params) { { destination: destination, header: header } } - - subject(:service) do - described_class.new( - destination: destination, - current_user: user, - params: params - ) - end - - describe '#execute' do - let(:response) { service.execute } - - context 'when no header is provided' do - let(:params) { super().merge(header: nil) } - - it 'does not destroy the header' do - expect { response }.not_to change { destination.headers.count } - end - - it 'has an error response' do - response = service.execute - - expect(response).to be_error - expect(response.errors).to match_array ['missing header param'] - end - end - - it_behaves_like 'header deletion' do - let(:audit_scope) { destination.group } - let(:extra_audit_context) { { stream_only: false } } - end - end -end diff --git a/ee/spec/services/audit_events/streaming/headers/update_service_spec.rb b/ee/spec/services/audit_events/streaming/headers/update_service_spec.rb deleted file mode 100644 index 2f5ac532b3fe2a..00000000000000 --- a/ee/spec/services/audit_events/streaming/headers/update_service_spec.rb +++ /dev/null @@ -1,50 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe AuditEvents::Streaming::Headers::UpdateService, feature_category: :audit_events do - let_it_be(:user) { create(:user) } - let_it_be(:event_type) { "audit_events_streaming_headers_update" } - - let(:header) { create(:audit_events_streaming_header, key: 'old', value: 'old') } - let(:destination) { header.external_audit_event_destination } - let(:params) do - { - header: header, - key: 'new', - value: 'new', - active: false - } - end - - subject(:service) do - described_class.new( - current_user: user, - destination: destination, - params: params - ) - end - - describe '#execute' do - subject(:response) { service.execute } - - context 'when no header is provided' do - let(:params) { super().merge(header: nil) } - - it 'does not update the header' do - expect { subject }.not_to change { header.reload.key } - expect(header.value).to eq 'old' - end - - it 'has an error response' do - expect(response).to be_error - expect(response.errors).to match_array ['missing header param'] - end - end - - it_behaves_like 'header updation' do - let(:audit_scope) { destination.group } - let(:extra_audit_context) { { stream_only: false } } - end - end -end diff --git a/ee/spec/services/audit_events/streaming/instance_headers/create_service_spec.rb b/ee/spec/services/audit_events/streaming/instance_headers/create_service_spec.rb deleted file mode 100644 index a023195ed277eb..00000000000000 --- a/ee/spec/services/audit_events/streaming/instance_headers/create_service_spec.rb +++ /dev/null @@ -1,54 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe AuditEvents::Streaming::InstanceHeaders::CreateService, feature_category: :audit_events do - let_it_be(:destination) { create(:instance_external_audit_event_destination) } - let(:params) { { destination: destination } } - let_it_be(:user) { create(:admin) } - let_it_be(:event_type) { "audit_events_streaming_instance_headers_create" } - - subject(:service) do - described_class.new( - params: params, - current_user: user - ) - end - - describe '#execute' do - before do - allow(Gitlab::Audit::Type::Definition).to receive(:defined?).with(event_type).and_return(true) - end - - subject(:response) { service.execute } - - it_behaves_like 'header creation validation errors' - - context 'when the header is created successfully' do - let(:params) { super().merge(key: 'a_key', value: 'a_value', active: true) } - - it_behaves_like 'header creation successful' - - it 'sends the audit streaming event', :aggregate_failures do - audit_context = { - name: 'audit_events_streaming_instance_headers_create', - author: user, - message: "Created custom HTTP header with key a_key." - } - expect(::Gitlab::Audit::Auditor) - .to receive(:audit) - .with(hash_including(audit_context)) - .and_call_original - - expect { response }.to change { AuditEvent.count }.from(0).to(1) - - expect(AuditEvent.last).to have_attributes( - author: user, - entity_id: Gitlab::Audit::InstanceScope.new.id, - entity_type: "Gitlab::Audit::InstanceScope", - details: include(custom_message: 'Created custom HTTP header with key a_key.') - ) - end - end - end -end diff --git a/ee/spec/services/audit_events/streaming/instance_headers/destroy_service_spec.rb b/ee/spec/services/audit_events/streaming/instance_headers/destroy_service_spec.rb deleted file mode 100644 index 42b94cb471aa01..00000000000000 --- a/ee/spec/services/audit_events/streaming/instance_headers/destroy_service_spec.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe AuditEvents::Streaming::InstanceHeaders::DestroyService, feature_category: :audit_events do - let_it_be(:header) { create(:instance_audit_events_streaming_header) } - let_it_be(:user) { create(:admin) } - let_it_be(:event_type) { "audit_events_streaming_instance_headers_destroy" } - - let(:destination) { header.instance_external_audit_event_destination } - - let(:params) { { header: header } } - - subject(:service) do - described_class.new( - params: params, - current_user: user - ) - end - - describe '#execute' do - let(:response) { service.execute } - - it_behaves_like 'header deletion' do - let(:audit_scope) { be_an_instance_of(Gitlab::Audit::InstanceScope) } - let(:extra_audit_context) { {} } - end - end -end diff --git a/ee/spec/services/audit_events/streaming/instance_headers/update_service_spec.rb b/ee/spec/services/audit_events/streaming/instance_headers/update_service_spec.rb deleted file mode 100644 index 7c87cf2649fb44..00000000000000 --- a/ee/spec/services/audit_events/streaming/instance_headers/update_service_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe AuditEvents::Streaming::InstanceHeaders::UpdateService, feature_category: :audit_events do - let(:header) { create(:instance_audit_events_streaming_header, key: 'old', value: 'old') } - let_it_be(:user) { create(:admin) } - let_it_be(:event_type) { "audit_events_streaming_instance_headers_update" } - - let(:params) do - { - header: header, - key: 'new', - value: 'new', - active: false - } - end - - subject(:service) do - described_class.new( - params: params, - current_user: user - ) - end - - describe '#execute' do - subject(:response) { service.execute } - - it_behaves_like 'header updation' do - let(:audit_scope) { be_an_instance_of(Gitlab::Audit::InstanceScope) } - let(:extra_audit_context) { {} } - end - end -end diff --git a/ee/spec/services/audit_events/user_impersonation_group_audit_event_service_spec.rb b/ee/spec/services/audit_events/user_impersonation_group_audit_event_service_spec.rb deleted file mode 100644 index 5d806a29fc6f0a..00000000000000 --- a/ee/spec/services/audit_events/user_impersonation_group_audit_event_service_spec.rb +++ /dev/null @@ -1,73 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe AuditEvents::UserImpersonationGroupAuditEventService do - let_it_be(:group) { create(:group) } - let_it_be(:user) { create(:user) } - let_it_be(:admin) { create(:admin) } - - let(:service) { described_class.new(impersonator: admin, user: user, remote_ip: '111.112.11.2', action: :started, created_at: 3.weeks.ago) } - - before do - stub_licensed_features(audit_events: true) - stub_licensed_features(admin_audit_log: true) - stub_licensed_features(extended_audit_events: true) - end - - context 'when user belongs to a single group' do - before do - group.add_developer(user) - end - - it 'creates audit events for both the instance and group level' do - freeze_time do - expect(::Gitlab::Audit::Auditor).to receive(:audit).twice.with(hash_including({ - name: "user_impersonation" - })).and_call_original - - expect { service.execute }.to change { AuditEvent.count }.by(2) - - event = AuditEvent.first - expect(event.details[:custom_message]).to eq("Started Impersonation") - expect(event.created_at).to eq(3.weeks.ago) - - group_audit_event = AuditEvent.last - expect(group_audit_event.details[:custom_message]).to eq("Instance administrator started impersonation of #{user.username}") - expect(group_audit_event.created_at).to eq(3.weeks.ago) - end - end - end - - context 'when user belongs to multiple groups' do - let!(:group2) { create(:group) } - let!(:group3) { create(:group) } - - before do - group.add_developer(user) - group2.add_developer(user) - group3.add_developer(user) - end - - it 'creates audit events for both the instance and group level' do - expect { service.execute }.to change { AuditEvent.count }.by(4) - - event = AuditEvent.first - expect(event.details[:custom_message]).to eq("Started Impersonation") - - group_audit_event = AuditEvent.last - expect(group_audit_event.details[:custom_message]).to eq("Instance administrator started impersonation of #{user.username}") - end - end - - context 'when user does not belong to any group' do - it 'creates audit events at the instance level' do - expect { service.execute }.to change { AuditEvent.count }.by(1) - - event = AuditEvent.last - expect(event.details[:custom_message]).to eq("Started Impersonation") - expect(event.author).to eq admin - expect(event.target_id).to eq user.id - end - end -end diff --git a/spec/services/audit_events/build_service_spec.rb b/spec/services/audit_events/build_service_spec.rb deleted file mode 100644 index 1dfbca823f48a3..00000000000000 --- a/spec/services/audit_events/build_service_spec.rb +++ /dev/null @@ -1,189 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe AuditEvents::BuildService, feature_category: :audit_events do - let(:author) { build_stubbed(:author, current_sign_in_ip: '127.0.0.1') } - let(:deploy_token) { build_stubbed(:deploy_token, user: author) } - let(:scope) { build_stubbed(:group) } - let(:target) { build_stubbed(:project) } - let(:ip_address) { '192.168.8.8' } - let(:message) { 'Added an interesting field from project Gotham' } - let(:additional_details) { { action: :custom } } - - subject(:service) do - described_class.new( - author: author, - scope: scope, - target: target, - message: message, - additional_details: additional_details, - ip_address: ip_address - ) - end - - describe '#execute', :request_store do - subject(:event) { service.execute } - - before do - allow(Gitlab::RequestContext.instance).to receive(:client_ip).and_return(ip_address) - end - - it 'sets correct attributes', :aggregate_failures do - freeze_time do - expect(event).to have_attributes( - author_id: author.id, - author_name: author.name, - entity_id: scope.id, - entity_type: scope.class.name) - - expect(event.details).to eq( - author_name: author.name, - author_class: author.class.name, - target_id: target.id, - target_type: target.class.name, - target_details: target.name, - custom_message: message, - action: :custom) - - expect(event.ip_address).to be_nil - expect(event.created_at).to eq(DateTime.current) - end - end - - context 'when IP address is not provided' do - let(:ip_address) { nil } - - it 'uses author current_sign_in_ip' do - expect(event.ip_address).to be_nil - end - end - - context 'when overriding target details' do - subject(:service) do - described_class.new( - author: author, - scope: scope, - target: target, - message: message, - target_details: "This is my target details" - ) - end - - it 'uses correct target details' do - expect(event.target_details).to eq("This is my target details") - end - end - - context 'when deploy token is passed as author' do - let(:service) do - described_class.new( - author: deploy_token, - scope: scope, - target: target, - message: message - ) - end - - it 'expect author to be user' do - expect(event.author_id).to eq(-2) - expect(event.author_name).to eq(deploy_token.name) - end - end - - context 'when deploy key is passed as author' do - let(:deploy_key) { build_stubbed(:deploy_key, user: author) } - - let(:service) do - described_class.new( - author: deploy_key, - scope: scope, - target: target, - message: message - ) - end - - it 'expect author to be deploy key' do - expect(event.author_id).to eq(-3) - expect(event.author_name).to eq(deploy_key.name) - end - end - - context 'when author is passed as UnauthenticatedAuthor' do - let(:service) do - described_class.new( - author: ::Gitlab::Audit::UnauthenticatedAuthor.new, - scope: scope, - target: target, - message: message - ) - end - - it 'sets author as unauthenticated user' do - expect(event.author).to be_an_instance_of(::Gitlab::Audit::UnauthenticatedAuthor) - expect(event.author_name).to eq('An unauthenticated user') - end - end - - context 'when attributes are missing' do - context 'when author is missing' do - let(:author) { nil } - - it { expect { service }.to raise_error(described_class::MissingAttributeError, "author") } - end - - context 'when target is missing' do - let(:target) { nil } - - it { expect { service }.to raise_error(described_class::MissingAttributeError, "target") } - end - - context 'when message is missing' do - let(:message) { nil } - - it { expect { service }.to raise_error(described_class::MissingAttributeError, "message") } - end - end - - context 'when scope is valid' do - where(:scope_type) do - [ - [:group], - [:project], - [:user] - ] - end - with_them do - let(:scope) do - case scope_type - when :group then create(:group) - when :project then create(:project) - when :user then create(:user) - end - end - - it 'does not raise error' do - expect { service.execute }.not_to raise_error - end - end - end - - context 'when scope is invalid' do - context 'when user namespace is passed as scope' do - let(:scope) { create(:user_namespace) } - - it 'raises error' do - expect { service.execute }.to raise_error(ArgumentError, "Invalid scope class: Namespaces::UserNamespace") - end - end - - context 'when scope is nil' do - let(:scope) { nil } - - it 'raises error' do - expect { service.execute }.to raise_error(ArgumentError, "Invalid scope class: NilClass") - end - end - end - end -end diff --git a/spec/services/audit_events/processor_spec.rb b/spec/services/audit_events/processor_spec.rb deleted file mode 100644 index 1b040b523f3319..00000000000000 --- a/spec/services/audit_events/processor_spec.rb +++ /dev/null @@ -1,406 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe AuditEvents::Processor, feature_category: :audit_events do - describe '.fetch' do - context 'when audit_event_json is present' do - let(:audit_event_json) { { id: 1, details: {} }.to_json } - let(:parsed_audit_event) { instance_double(::AuditEvent) } - - it 'processes the JSON and returns an audit event' do - expect(described_class).to receive(:fetch_from_json).with(audit_event_json).and_return(parsed_audit_event) - - expect(described_class.fetch(audit_event_json: audit_event_json)).to eq(parsed_audit_event) - end - end - - context 'when audit_event_id is present' do - let(:audit_event_id) { 1 } - let(:model_class) { '::AuditEvents::GroupAuditEvent' } - let(:found_audit_event) { instance_double(::AuditEvents::GroupAuditEvent) } - - it 'finds the audit event by ID and returns it' do - expect(described_class).to receive(:fetch_from_id).with(audit_event_id, - model_class).and_return(found_audit_event) - - expect(described_class.fetch(audit_event_id: audit_event_id, model_class: model_class)).to eq(found_audit_event) - end - end - - context 'when both audit_event_id and audit_event_json are provided' do - let(:audit_event_id) { 1 } - let(:audit_event_json) { { id: 2, details: {} }.to_json } - let(:parsed_audit_event) { instance_double(::AuditEvent) } - - it 'prioritizes audit_event_json and ignores audit_event_id' do - expect(described_class).to receive(:fetch_from_json).with(audit_event_json).and_return(parsed_audit_event) - expect(described_class).not_to receive(:fetch_from_id) - - expect(described_class.fetch(audit_event_id: audit_event_id, - audit_event_json: audit_event_json)).to eq(parsed_audit_event) - end - end - - context 'when neither audit_event_id nor audit_event_json are provided' do - it 'returns nil' do - expect(described_class.fetch).to be_nil - end - end - - context 'when an error occurs' do - let(:audit_event_id) { 1 } - - before do - allow(described_class).to receive(:fetch_from_id).and_raise(StandardError.new('test error')) - end - - it 'tracks the exception and returns nil' do - expect(::Gitlab::ErrorTracking).to receive(:track_exception).with( - an_instance_of(StandardError), - hash_including(audit_event_id: audit_event_id, model_class: nil, audit_event_json: nil) - ) - - result = described_class.fetch(audit_event_id: audit_event_id) - expect(result).to be_nil - end - end - end - - describe '.fetch_from_id' do - let(:audit_event_id) { 1 } - - context 'when model class is provided' do - let(:model_class) { '::AuditEvents::GroupAuditEvent' } - let(:group_audit_event) { instance_double(::AuditEvents::GroupAuditEvent) } - - it 'finds the audit event using the provided model class' do - expect(::AuditEvents::GroupAuditEvent).to receive(:find).with(audit_event_id).and_return(group_audit_event) - - result = described_class.fetch_from_id(audit_event_id, model_class) - expect(result).to eq(group_audit_event) - end - - context 'when the record is not found' do - before do - allow(::AuditEvents::GroupAuditEvent).to receive(:find).and_raise(ActiveRecord::RecordNotFound) - end - - it 'tracks the error and returns nil' do - expect(::Gitlab::ErrorTracking).to receive(:track_exception).with( - an_instance_of(ActiveRecord::RecordNotFound), - hash_including(audit_event_id: audit_event_id, model_class: model_class) - ) - - result = described_class.fetch_from_id(audit_event_id, model_class) - expect(result).to be_nil - end - end - end - - context 'when model class is not provided' do - let(:audit_event) { instance_double(::AuditEvent) } - - it 'finds the audit event using the AuditEvent model' do - expect(::AuditEvent).to receive(:find_by_id).with(audit_event_id).and_return(audit_event) - - result = described_class.fetch_from_id(audit_event_id, nil) - expect(result).to eq(audit_event) - end - end - end - - describe '.determine_audit_model_entity' do - let(:group) { create(:group) } - let(:project) { create(:project) } - let(:user) { create(:user) } - - context 'with group_id present' do - let(:audit_event_json) { { group_id: group.id }.with_indifferent_access } - - it 'returns GroupAuditEvent model and group entity' do - allow(::Group).to receive(:find).with(group.id).and_return(group) - - model_class, entity = described_class.determine_audit_model_entity(audit_event_json) - - expect(model_class).to eq(::AuditEvents::GroupAuditEvent) - expect(entity).to eq(group) - end - end - - context 'with project_id present' do - let(:audit_event_json) { { project_id: project.id }.with_indifferent_access } - - it 'returns ProjectAuditEvent model and project entity' do - allow(::Project).to receive(:find).with(project.id).and_return(project) - - model_class, entity = described_class.determine_audit_model_entity(audit_event_json) - - expect(model_class).to eq(::AuditEvents::ProjectAuditEvent) - expect(entity).to eq(project) - end - end - - context 'with user_id present' do - let(:audit_event_json) { { user_id: user.id }.with_indifferent_access } - - it 'returns UserAuditEvent model and user entity' do - allow(::User).to receive(:find).with(user.id).and_return(user) - - model_class, entity = described_class.determine_audit_model_entity(audit_event_json) - - expect(model_class).to eq(::AuditEvents::UserAuditEvent) - expect(entity).to eq(user) - end - end - - context 'with no entity ID present' do - let(:audit_event_json) { { details: 'some details' }.with_indifferent_access } - - it 'returns InstanceAuditEvent model and instance symbol' do - model_class, entity = described_class.determine_audit_model_entity(audit_event_json) - - expect(model_class).to eq(::AuditEvents::InstanceAuditEvent) - expect(entity).to eq(:instance) - end - end - - context 'when entity is not found' do - let(:non_existing_id) { non_existing_record_id } - let(:audit_event_json) { { group_id: non_existing_id }.with_indifferent_access } - - it 'raises RecordNotFound error' do - allow(::Group).to receive(:find).with(non_existing_id).and_raise(ActiveRecord::RecordNotFound) - - expect do - described_class.determine_audit_model_entity(audit_event_json) - end.to raise_error(ActiveRecord::RecordNotFound) - end - end - end - - describe '.create_scoped_audit_event' do - let(:audit_event_json) do - { - id: 123, - group_id: 1, - project_id: 2, - user_id: 3, - entity_type: 'Group', - entity_id: 1, - details: { custom_message: 'test message' } - }.with_indifferent_access - end - - context 'for GroupAuditEvent' do - let(:model_class) { ::AuditEvents::GroupAuditEvent } - - it 'filters out excluded fields for group audit events' do - event = described_class.create_scoped_audit_event(model_class, audit_event_json) - - expect(event).to be_a(::AuditEvents::GroupAuditEvent) - expect(event.group_id).to eq(1) - expect(event.attributes).not_to include('project_id') - expect(event.attributes).not_to include('user_id') - expect(event.attributes).not_to include('entity_type') - expect(event.attributes).not_to include('entity_id') - end - end - - context 'for ProjectAuditEvent' do - let(:model_class) { ::AuditEvents::ProjectAuditEvent } - - it 'filters out excluded fields for project audit events' do - event = described_class.create_scoped_audit_event(model_class, audit_event_json) - - expect(event).to be_a(::AuditEvents::ProjectAuditEvent) - expect(event.project_id).to eq(2) - expect(event.attributes).not_to include('group_id') - expect(event.attributes).not_to include('user_id') - expect(event.attributes).not_to include('entity_type') - expect(event.attributes).not_to include('entity_id') - end - end - - context 'for UserAuditEvent' do - let(:model_class) { ::AuditEvents::UserAuditEvent } - - it 'filters out excluded fields for user audit events' do - event = described_class.create_scoped_audit_event(model_class, audit_event_json) - - expect(event).to be_a(::AuditEvents::UserAuditEvent) - expect(event.user_id).to eq(3) - expect(event.attributes).not_to include('group_id') - expect(event.attributes).not_to include('project_id') - expect(event.attributes).not_to include('entity_type') - expect(event.attributes).not_to include('entity_id') - end - end - - context 'for InstanceAuditEvent' do - let(:model_class) { ::AuditEvents::InstanceAuditEvent } - - it 'filters out all entity fields for instance audit events' do - event = described_class.create_scoped_audit_event(model_class, audit_event_json) - - expect(event).to be_a(::AuditEvents::InstanceAuditEvent) - expect(event.attributes).not_to include('group_id') - expect(event.attributes).not_to include('project_id') - expect(event.attributes).not_to include('user_id') - expect(event.attributes).not_to include('entity_type') - expect(event.attributes).not_to include('entity_id') - end - end - - context 'for unknown model class' do - # Create a test double that mimics ActiveRecord behavior - let(:model_class) do - Class.new do - attr_reader :attributes - - def initialize(attrs = {}) - @attributes = attrs - attrs.each do |key, value| - define_singleton_method(key) { value } - end - end - end - end - - it 'only filters out entity_type and entity_id fields' do - event = described_class.create_scoped_audit_event(model_class, audit_event_json) - - expect(event).to be_an_instance_of(model_class) - expect(event.group_id).to eq(1) - expect(event.project_id).to eq(2) - expect(event.user_id).to eq(3) - expect(event.attributes).not_to include('entity_type') - expect(event.attributes).not_to include('entity_id') - end - end - end - - describe '.fetch_from_json' do - let(:group) { create(:group) } - let(:author) { create(:user) } - let(:base_json) do - { - author_id: author.id, - entity_id: group.id, - entity_type: 'Group', - created_at: Time.current, - details: { custom_message: 'test message' } - } - end - - context 'when feature flag is enabled' do - before do - stub_feature_flags(stream_audit_events_from_new_tables: true) - end - - context 'with group_id present' do - let(:audit_event_json) do - base_json.merge( - group_id: group.id - ).to_json - end - - it 'creates a GroupAuditEvent' do - allow(described_class).to receive(:determine_audit_model_entity).and_return([::AuditEvents::GroupAuditEvent, - group]) - allow(::Gitlab::Audit::FeatureFlags).to receive(:stream_from_new_tables?).with(group).and_return(true) - - result = described_class.fetch_from_json(audit_event_json) - - expect(result).to be_a(::AuditEvents::GroupAuditEvent) - end - end - - context 'with project_id present' do - let(:project) { create(:project) } - let(:audit_event_json) do - base_json.merge( - project_id: project.id - ).to_json - end - - it 'creates a ProjectAuditEvent' do - allow(described_class).to receive(:determine_audit_model_entity).and_return([ - ::AuditEvents::ProjectAuditEvent, project - ]) - allow(::Gitlab::Audit::FeatureFlags).to receive(:stream_from_new_tables?).with(project).and_return(true) - - result = described_class.fetch_from_json(audit_event_json) - - expect(result).to be_a(::AuditEvents::ProjectAuditEvent) - end - end - end - - context 'when feature flag is disabled' do - before do - stub_feature_flags(stream_audit_events_from_new_tables: false) - allow(described_class).to receive(:determine_audit_model_entity).and_return([::AuditEvents::GroupAuditEvent, - group]) - allow(::Gitlab::Audit::FeatureFlags).to receive(:stream_from_new_tables?).with(group).and_return(false) - end - - let(:audit_event_json) do - base_json.merge( - group_id: group.id - ).to_json - end - - it 'creates a base AuditEvent' do - result = described_class.fetch_from_json(audit_event_json) - - expect(result).to be_a(::AuditEvent) - end - - it 'filters out group_id, project_id, and user_id fields' do - result = described_class.fetch_from_json(audit_event_json) - - expect(result.attributes).not_to include('group_id') - expect(result.attributes).not_to include('project_id') - expect(result.attributes).not_to include('user_id') - end - end - - context 'when JSON parsing fails' do - let(:invalid_json) { '{invalid_json' } - - it 'tracks the exception and returns nil' do - expect(::Gitlab::ErrorTracking).to receive(:track_exception).with( - an_instance_of(JSON::ParserError), - hash_including(audit_event_json: invalid_json) - ) - - result = described_class.fetch_from_json(invalid_json) - expect(result).to be_nil - end - end - - context 'when entity lookup fails' do - let(:non_existing_id) { non_existing_record_id } - let(:audit_event_json) do - { - group_id: non_existing_id, - author_id: create(:user).id, - entity_id: non_existing_id, - entity_type: 'Group', - created_at: Time.current - }.to_json - end - - it 'tracks the exception and returns nil' do - expect(::Gitlab::ErrorTracking).to receive(:track_exception).with( - an_instance_of(ActiveRecord::RecordNotFound), - hash_including(audit_event_json: a_string_matching(/#{non_existing_id}/)) - ) - - result = described_class.fetch_from_json(audit_event_json) - expect(result).to be_nil - end - end - end -end -- GitLab