# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'contrast/api/dtm.pb' require 'contrast/api/decorators/address' require 'contrast/components/interface' require 'contrast/utils/string_utils' require 'contrast/utils/timer' module Contrast module Api module Decorators # Used to decorate the {Contrast::Api::Dtm::HttpRequest} protobuf model # so it can own some of the data massaging required for Request dtm. Only # works as an extension of that class. module HttpRequest include Contrast::Components::Interface access_component :agent OMITTED_BODY = '{{body-omitted-by-contrast}}' def self.included klass klass.extend(ClassMethods) end # Append the sender and receiver to self. # # @param request [Contrast::Agent::Request] def append_addresses request self.sender = Contrast::Api::Dtm::Address.build_sender(request) self.receiver = Contrast::Api::Dtm::Address.build_receiver end # Append the connection to self. # # @param request [Contrast::Agent::Request] def append_connection request self.protocol = Contrast::Utils::StringUtils.force_utf8(request.scheme) self.version = '1.1' # currently not in rack request; hard-coding self.method = Contrast::Utils::StringUtils.force_utf8(request.request_method) self.raw = Contrast::Utils::StringUtils.force_utf8(request.rack_request.path_info) end # Append the parameters to self, using the preferred # normalized_request_params field. # # @param request [Contrast::Agent::Request] def append_params request request.parameters.each do |k, v| append_pair(normalized_request_params, k, v) end end # Append the normalized headers to self, using the preferred # request_headers field. # # @param request [Contrast::Agent::Request] def append_headers request request.headers.each do |k, v| next unless k && v next if v.is_a?(Hash) key = Contrast::Utils::StringUtils.force_utf8(k) val = Contrast::Utils::StringUtils.force_utf8(v) request_headers[key] = val end request.cookies.each do |k, v| append_pair(normalized_cookies, k, v) end end # Append the body to self, using the preferred # request_body_binary field. # # @param request [Contrast::Agent::Request] def append_body request self.document_type = Contrast::Utils::StringUtils.force_utf8(request.document_type) self.request_body_binary = if omit_body?(request) OMITTED_BODY else Contrast::Utils::StringUtils.force_utf8(request.body) end return if request.file_names.empty? request.file_names.each do |name, filename| pair = Contrast::Api::Dtm::SimplePair.new pair.key = Contrast::Utils::StringUtils.force_utf8(name) pair.value = Contrast::Utils::StringUtils.force_utf8(filename) multipart_headers << pair end end def omit_body? request return true if AGENT.omit_body? return false if request.document_type != :NORMAL request.content_type&.include?('multipart/form-data') end def append_pair map, key, value return unless key && value return if value.is_a?(Hash) safe_key = Contrast::Utils::StringUtils.force_utf8(key) map[safe_key] ||= Contrast::Api::Dtm::Pair.new map[safe_key].key = safe_key map[safe_key].values << Contrast::Utils::StringUtils.force_utf8(value) end # Used to add class methods to the ApplicationUpdate class on inclusion of the decorator module ClassMethods include Contrast::Components::Interface access_component :scope # Convert our Request into a DTM # @param request [Contrast::Agent::Request] # @return [Contrast::Api::Dtm::HttpRequest] def build request with_contrast_scope do msg = new msg.uuid = Contrast::Utils::StringUtils.force_utf8(__id__) msg.timestamp_ms = Contrast::Utils::Timer.now_ms.to_i msg.append_addresses(request) msg.append_connection(request) msg.append_params(request) msg.append_headers(request) msg.append_body(request) msg end end end end end end end Contrast::Api::Dtm::HttpRequest.include(Contrast::Api::Decorators::HttpRequest)