# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'contrast/api/communication/response_processor' require 'contrast/api/decorators/application_settings' require 'contrast/agent/reporting/reporting_utilities/response' require 'contrast/agent/reporting/reporting_utilities/response_handler_utils' require 'contrast/agent/reporting/reporting_utilities/response_handler_mode' require 'contrast/api/decorators/server_features' require 'contrast/components/logger' require 'json' module Contrast module Agent module Reporting # This class will facilitate the Response capture and analysis functionality. class ResponseHandler include Contrast::Components::Logger::InstanceMethods include Contrast::Agent::Reporting::ResponseHandlerUtils # 15 min TIMEOUT = 900.cs__freeze # Process the response from TS # # @param response [Net::HTTP::Response, nil] # @return response [Net::HTTP::Response, nil] def process response logger.debug('Reporter Received a response') return unless analyze_response?(response) # Handle the response body and obtain server_features or app_settings report_response = convert_response(response) return unless report_response # Update Server Features and Application Settings to provide current agent settings update_agent_settings(report_response) # Process any reactions, including message logging and shutting down update_reaction(report_response) update_ruleset(report_response) logger.trace('Agent settings updated in response to TeamServer', protect_on: ::Contrast::PROTECT.enabled?, assess_on: ::Contrast::ASSESS.enabled?) response rescue StandardError => e logger.error('Unable to process response from TeamServer', e) nil end # If sleep is true puts reporting service to sleep. def sleep? @_sleep = wake_up if @_sleep.nil? @_sleep end # For how long the agent should wait until retry # to send message # # @return timeout [Integer] the time for reporter # to be suspended. def timeout @_timeout ||= TIMEOUT end def mode @_mode ||= Contrast::Agent::Reporting::ResponseHandlerMode.new end # Wakes the reporting service def wake_up @_sleep = false end private # Handles the errors code received from TS and takes appropriate action. # If we are here the response.code is an error that needs handling [4XX] # # @param response [Net::HTTP::Response] def handle_error response case response&.code when ERROR_CODES[:message_not_sent] handle_response_errors response, UNSUCCESSFULLY_RECEIVED_MSG, mode.running when ERROR_CODES[:access_forbidden] handle_response_errors response, FORBIDDEN_MSG, mode.running when ERROR_CODES[:access_forbidden_no_action] handle_response_errors response, FORBIDDEN_NO_ACTION_MSG, mode.running when ERROR_CODES[:application_do_not_exist] handle_response_errors response, APP_NON_EXISTENT_MSG, mode.disabled when ERROR_CODES[:unprocessable_entity] handle_response_errors response, UNPROCESSABLE_ENTITY_MSG, mode.disabled when ERROR_CODES[:too_many_requests] handle_response_errors response, RETRY_AFTER_MSG, mode.resending else logger.debug('Response Error code could not be processed') end end # Suspend the reporter and try again in time. If not set # the timeout is set to 15 min As default: # # @param message [String] Message to log. # @param timeout [Integer,nil] The timeout to wait and retry after. # @param error_message [String, nil] Error message if any received. def suspend_reporting message, timeout, error_message @_timeout = timeout || Contrast::Agent::Reporting::ResponseHandler::TIMEOUT log_debug_msg message, timeout: @_timeout, error_message: error_message || 'none' @_sleep = true end # Log what we've received. # # @param message [String] Message to log # @param info_hash [Hash] information about the context to log. def log_debug_msg message, info_hash logger.debug(message, info_hash) end end end end end