diff --git a/app/services/audit_event_service.rb b/app/services/audit_event_service.rb deleted file mode 100644 index 518d6894e1c8029d486b177dc0e35e3e0e9c74f4..0000000000000000000000000000000000000000 --- 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/app/services/audit_events/build_service.rb b/app/services/audit_events/build_service.rb deleted file mode 100644 index 29ead8caf7e1ca5212b3e6e092c444321818750a..0000000000000000000000000000000000000000 --- 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 c121b0e811bc385e057c9c12b802dc83fdd8db80..0000000000000000000000000000000000000000 --- 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 8d445b1a4fd30ef53e01fd415749ce870e141e22..0000000000000000000000000000000000000000 --- 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 82a0c381200c33b7ad887d9a9ef6f4250e048f46..0000000000000000000000000000000000000000 --- 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 e9ee1f864792bb1bb73141a0a5575405d7ee0b03..0000000000000000000000000000000000000000 --- 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 6d794096159aee89d428efda95f1116b55c067d2..0000000000000000000000000000000000000000 --- 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 812ad556c6d5d319de6c951fc10d6b49288a6fa0..0000000000000000000000000000000000000000 --- 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 8859b085071526d90ecf880669de501f9dda9d69..0000000000000000000000000000000000000000 --- 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 32022379137cf1f416ae1cc461f53e35c6029799..0000000000000000000000000000000000000000 --- 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 14716c04ff37d8b9482b6999992d01ff697e72f4..0000000000000000000000000000000000000000 --- 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 99e12d69b1eb8fa27140d482b63df4085ed5f98a..0000000000000000000000000000000000000000 --- 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 483f105d37e247925ebf182d40c8349feac0882a..0000000000000000000000000000000000000000 --- 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 01914c8d9e51fee9b18542059705121e7772d435..0000000000000000000000000000000000000000 --- 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 a679d5fe6f0677c9110d63bea18ce12415b42111..0000000000000000000000000000000000000000 --- 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 baa943bdf00984376246153b8d7567f2fd093768..0000000000000000000000000000000000000000 --- 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 5e145e5196aebd815043bf8aa32335d685a497d4..0000000000000000000000000000000000000000 --- 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 d20ebada8f771c023f9b26e8180d414e2e4d0d06..0000000000000000000000000000000000000000 --- 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 7301a5aa25bc0b4678de070a12c7ae5c2abdc09d..0000000000000000000000000000000000000000 --- 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 9dc64be722eec64093d9ac061b129181e1c18507..0000000000000000000000000000000000000000 --- 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 c2cbe016d5bfe14cf865019f5203fa02442d59d7..0000000000000000000000000000000000000000 --- 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 67887f9e7bcfc8a8e4a9d8037ca7b913b842491e..0000000000000000000000000000000000000000 --- 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 354e2ef023d255a5c26be45f5eeb40bfb88a88e6..0000000000000000000000000000000000000000 --- 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 960971879c4124716d40297185515871b50fa786..0000000000000000000000000000000000000000 --- 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 d4e3b87681349a740ade4c8bac0fc87bb2fb5ac9..0000000000000000000000000000000000000000 --- 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 2ac2e66b12a626c78e2f6af33c52d4fb02c7c6f0..0000000000000000000000000000000000000000 --- 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 67482f5392bf6d05feda00273489e5dc8b99987f..0000000000000000000000000000000000000000 --- 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 292378baaa643a530692a67e6be71165633944ad..0000000000000000000000000000000000000000 --- 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 d0326e0858364f1a443abe840e3719ade521c0d0..0000000000000000000000000000000000000000 --- 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 750f48c30e9ae83bbbc766559ebf72634e2fa0e1..0000000000000000000000000000000000000000 --- 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 3a64200ec9c561894f69fe4cf71b1d16a88de647..0000000000000000000000000000000000000000 --- 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 1625241d4dd9511306f3251436978abc05c7d777..0000000000000000000000000000000000000000 --- 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 6747654c3a5053d3d41b53249969e8841dfa9bb5..0000000000000000000000000000000000000000 --- 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 d2f330a46b516a1eb9062cac7c6c1ba4d041a749..0000000000000000000000000000000000000000 --- 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 06b91807868ac6fdaf81f461c807c600b3c51173..0000000000000000000000000000000000000000 --- 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 8cdc762a7e0ac8a796ec030f50bbb9925ea93820..0000000000000000000000000000000000000000 --- 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 b819e830b175eeef02afd8c1e14ef898ed6249ed..0000000000000000000000000000000000000000 --- 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 d8b44f1f8d1e885331f55d76100f51e6c4da6279..0000000000000000000000000000000000000000 --- 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 18286a779e51083caa1e11013656816c156ed122..0000000000000000000000000000000000000000 --- 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 4bc1f7aaae2833e3c63b728484bcc8efbb2b4798..0000000000000000000000000000000000000000 --- 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 2f5ac532b3fe2a7ef9484ea5eef558d3dc153d9b..0000000000000000000000000000000000000000 --- 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 a023195ed277eb844a20bda237b7493d50db0168..0000000000000000000000000000000000000000 --- 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 42b94cb471aa0145ddb57b99891dd2e72b24c483..0000000000000000000000000000000000000000 --- 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 7c87cf2649fb441803d0db497bde1ca8665321e8..0000000000000000000000000000000000000000 --- 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 5d806a29fc6f0a2f53d04a1694aa92460525a8b2..0000000000000000000000000000000000000000 --- 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_event_service_spec.rb b/spec/services/audit_event_service_spec.rb deleted file mode 100644 index 35e0cf5846c962a84311f472b79acbfd08ef83b3..0000000000000000000000000000000000000000 --- 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 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 1dfbca823f48a395a352d6d653ee009e9b0b6e5c..0000000000000000000000000000000000000000 --- 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 1b040b523f331953b7322537112983eed4fa63c5..0000000000000000000000000000000000000000 --- 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