# Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'contrast/agent/reporting/reporting_events/application_activity' require 'contrast/utils/timer' module Contrast module Utils module Reporting # ApplicationActivityBatchUtils handles batching and reporting of ApplicationActivity events to TeamServer at a # set interval module ApplicationActivityBatchUtils DEFAULT_REPORTING_INTERVAL_MS = 30_000.cs__freeze # @return [Integer] time when activity batch was created in ms def batch_age @_batch_age ||= Contrast::Utils::Timer.now_ms end # Merge a ApplicationActivity into the ApplicationActivityBatch # @param activity [Contrast::Agent::Reporting::ApplicationActivity] from a RequestContext def add_activity_to_batch activity return unless activity return if activity.defend.attackers.empty? merge_attackers(activity) activity_batch.attach_inventory(activity.inventory) unless activity.inventory.empty? end # If the batch can be reported, mask the data and add it to the reporting queue, then reset the activity_batch def report_batch return unless report_batch? return unless (reporter = Contrast::Agent.reporter) Contrast::Agent::Reporting::Masker.mask(activity_batch) reporter&.send_event(activity_batch) reset_activity_batch end # @return Contrast::Agent::Reporting::ApplicationActivity def activity_batch return @activity_batch unless @activity_batch.nil? reset_activity_batch @activity_batch end private def merge_attackers activity return if activity.defend.attackers.empty? activity.defend.attackers.each do |attacker| if (existing = activity_batch.defend.find_existing_attacker_activity(attacker)) attacker.protection_rules.each_key do |key| activity_batch.defend.attach_existing(existing, attacker, key) end else activity_batch.defend.attackers << attacker end end end # resets the activity_batch object and it's batch_age def reset_activity_batch @_batch_age = Contrast::Utils::Timer.now_ms @activity_batch = Contrast::Agent::Reporting::ApplicationActivity.new end # We need to check for attackers to see if the batch is newly created along side with the age check. # In the reader we reset the batch and the age is correct but the event is empty. We need to prevent # sending of empty event. # # @return [Boolean] if the age of the batch is outside the reporting interval def report_batch? activity_batch.defend.attackers.any? && reporting_interval <= (Contrast::Utils::Timer.now_ms - batch_age) end # @return [Integer] interval to report to TeamServer in seconds def reporting_interval Contrast::AGENT.polling.batch_reporting_interval_ms.to_i || DEFAULT_REPORTING_INTERVAL_MS end end end end end