# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'contrast/agent/worker_thread' require 'contrast/agent/reporting/report' require 'contrast/components/logger' require 'contrast/agent/reporting/reporting_events/agent_startup' module Contrast module Agent # This module will hold everything essential to reporting to TeamServer class Reporter < WorkerThread include Contrast::Components::Logger::InstanceMethods include Contrast::Utils::ObjectShare class << self # check if we can report to TS # # @return[Boolean] true if bypass is enabled, or false if bypass disabled def enabled? @_enabled = Contrast::CONTRAST_SERVICE.use_agent_communication? if @_enabled.nil? @_enabled end end def client @_client ||= Contrast::Agent::Reporting::ReporterClient.new end def connection @_connection ||= client.initialize_connection end def attempt_to_start? unless cs__class.enabled? logger.warn('Reporter service is disabled!') return false end logger.debug('Attempting to start Reporter thread') unless running? true end def start_thread! return if running? client.startup!(connection) @_thread = Contrast::Agent::Thread.new do logger.debug('Starting background Reporter thread.') loop do # TODO: RUBY-99999 # The client and connection are being used in multiple threads/ concurrently, and that's not okay. We need # to figure out why that is and lock it so that it isn't. next unless client && connection process_event(queue.pop) end end end # Suspend the Reporter and try sending the event after the timeout. # The timeout is either default 15 min or received via TS response. # # @param event [Contrast::Agent::Reporting::ReportingEvent] Freshly pop-ed event. def handle_resend event sleep(client.timeout) if client.sleep? # Retry once than discard the event. This is trigger on too many events of # same kind error. client.send_event(event, connection) if client.mode.status == client.mode.resending client.mode.reset_mode client.wake_up end # @param event [Contrast::Agent::Reporting::ReportingEvent] def send_event event if ::Contrast::AGENT.disabled? logger.warn('Attempted to queue event with Agent disabled', caller: caller, event: event) return end return unless event return unless cs__class.enabled? logger.debug('Enqueued event for sending', event_type: event.cs__class) queue << event end # Use this to bypass the messaging queue and leave response processing to the caller # # @param event [Contrast::Agent::Reporting::ReportingEvent] # @return [Net::HTTPResponse, nil] def send_event_immediately event if ::Contrast::AGENT.disabled? logger.warn('Reporter attempted to send event immediately with Agent disabled', caller: caller, event: event) return end client.send_event(event, connection, send_immediately: true) rescue StandardError => e logger.error('Could not send message to TeamServer from Reporter queue.', e) end def delete_queue! @_queue&.clear @_queue&.close @_queue = nil end def stop! return unless running? super delete_queue! end private def queue @_queue ||= Queue.new end # @param event [Contrast::Agent::Reporting::ReportingEvent] def process_event event client.send_event(event, connection) handle_resend(event) if client.mode.status == client.mode.resending rescue StandardError => e logger.error('Could not send message to TeamServer from Reporter queue.', e) end end end end