# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'contrast/components/logger' require 'contrast/components/scope' require 'contrast/agent/reporting/reporting_events/application_startup' require 'contrast/agent/reporting/reporting_utilities/reporter_client' require 'contrast/agent/reporting/reporting_utilities/endpoints' module Contrast module Agent module Reporting # This module holds utilities required by the reporting service client module ReporterClientUtils include Contrast::Components::Logger::InstanceMethods include Contrast::Components::Scope::InstanceMethods include Contrast::Agent::Reporting::Endpoints # List the events that need to be sent when agent starts up. # The order here matters -- DO NOT CHANGE IT!!! >_< - HM # # If you add more, update the test in reporter_client_spec.rb STARTUP_EVENTS = [ Contrast::Agent::Reporting::AgentStartup, Contrast::Agent::Reporting::ApplicationStartup ].cs__freeze def audit @_audit ||= Contrast::Agent::Reporting::Audit.new end private # Send Agent Startup event # # @param connection [Net::HTTP] open connection def send_agent_startup connection logger.debug('Preparing to send startup messages') STARTUP_EVENTS.each do |event| startup_event = event.new send_event(startup_event, connection) rescue StandardError => e handle_error(startup_event, e) end logger.debug('Startup messages sent') end # This method will build headers of the request required for TS communication # # @param request [Net::HTTPRequest] def build_headers request app_version = @headers.app_version request['API-Key'] = @headers.api_key request['Application-Language'] = @headers.app_language request['Application-Name'] = @headers.app_name request['Application-Path'] = @headers.app_path request['Application-Version'] = app_version if app_version request['Authorization'] = @headers.authorization request['Content-Type'] = @headers.content_type request['Server-Name'] = @headers.server_name request['Server-Path'] = @headers.server_path request['Server-Type'] = @headers.server_type request['X-Contrast-Agent'] = @headers.agent_version request['X-Contrast-Header-Encoding'] = @headers.encoding request end # Handles standard error case, logs and set status for failure # # @param event [Contrast::Agent::Reporting::ReportingEvent] # One of the DTMs valid for the event field of Contrast::Api::Dtm::Message # @param error_msg [StandardError] # @return nil [NilClass] to be passed as response def handle_error event, error_msg status.failure! logger.error('Unable to send message.', error_msg, client: Contrast::Agent::Reporting::ReporterClient::SERVICE_NAME, event_id: event&.__id__, event_type: event&.cs__class&.cs__name) nil end # Handles response processing and sets status # # @param event [Contrast::Agent::Reporting::ReportingEvent] The event sent to TeamServer. # @param response [Net::HTTP::Response] def process_settings_response response, event res = response_handler.process(response, event) status.success! if res res end # Given a response from preflght, when the finding hash is desired, then send the finding to which it pertains. # The method accepts any Contrast::Agent::Reporting::ReportingEvent, but will short circuit if it is not a # Contrast::Agent::Reporting::Preflight. # # @param event [Contrast::Agent::Reporting::ReportingEvent] The event to send to TeamServer. Really a # child of the ReportingEvent rather than a literal one. # @param response [Net::HTTPResponse,nil] The response we handle and read from # @param connection [Net::HTTP] open connection def process_preflight_response event, response, connection return unless event.cs__is_a?(Contrast::Agent::Reporting::Preflight) return unless response&.body && connection findings_to_return = response.body.split(',').delete_if { |el| el.include?('*') } findings_to_return.each do |index| preflight_message = event.messages[index.to_i] corresponding_finding = Contrast::Agent::Reporting::ReportingStorage.delete(preflight_message.data) next unless corresponding_finding send_event(corresponding_finding, connection) end rescue StandardError => e logger.error('Unable to handle response', e) end # Convert the given event into an appropriate Net::HTTPRequest object, setting the request headers and # assigning endpoint the endpoint appropriate for the event and casting its hash to a JSON body. # # @param event event [Contrast::Agent::Reporting::ReportingEvent] The event to send to TeamServer. Really a # child of the ReportingEvent rather than a literal one. # @return [Net::HTTP::Post,Net::HTTP::Put] def build_request event with_contrast_scope do request = case event.event_method when :PUT Net::HTTP::Put.new(event.event_endpoint) when :GET Net::HTTP::Get.new(event.event_endpoint) else # :POST Net::HTTP::Post.new(event.event_endpoint) end build_headers(request) event.attach_headers(request) request.body = event.event_json request end end end end end end