# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'digest' require 'json' require 'contrast/components/logger' require 'contrast/agent/reporting/reporting_events/application_reporting_event' require 'contrast/utils/object_share' require 'contrast/utils/duck_utils' module Contrast module Agent module Reporting # This is the new Route Observation class which will include all the needed information for the new reporting # system to relay this information in the Route Observation messages. These observations are used by TeamServer # to construct the route coverage information for the assess feature. They represent those methods which map to # externally accessible endpoints within the application, as registered to the application framework. This also # includes the literal URL and HTTP Verb used to invoke them, as they must have been called at this point to be # recorded. class ObservedRoute < Contrast::Agent::Reporting::ApplicationReportingEvent include Contrast::Components::Logger::InstanceMethods # @param [String] the method signature used to uniquely identify the coverage report. attr_accessor :signature # @param [String] the normalized URL used to access the method in the route. attr_accessor :url # @param [String] the HTTP Verb used to access the method in the route. attr_accessor :verb # @param [Array] the sources of user input accessed during this # request. Used for remediation determinations in TeamServer. attr_reader :sources def initialize @event_endpoint = Contrast::Agent::Reporting::Endpoints.observed_route @sources = [] @verb = Contrast::Utils::ObjectShare::EMPTY_STRING super() end def file_name 'routes-observed' end # Convert the instance variables on the class, and other information, into the identifiers required for # TeamServer to process the JSON form of this message. # # @return [Hash] # @raise [ArgumentError] def to_controlled_hash begin validate rescue ArgumentError => e logger.error('ObservedRoute validation failed with: ', e) return end { session_id: ::Contrast::ASSESS.session_id, sources: @sources.map(&:to_controlled_observation_hash), signature: @signature, verb: @verb, url: @url }.compact end # Generates a hash id for the observed route, based on the sources, signature, verb, and url. # # @return [String] # def hash_id hashable_data = to_controlled_hash.reject { |key, _value| key == :session_id } # rubocop:disable Style/HashExcept hashable_data[:sources] = hashable_data[:sources].sort_by { |s| s[:name] } Digest::SHA2.new(256).hexdigest(hashable_data.to_s) end # @raise [ArgumentError] def validate raise(ArgumentError, "#{ self } did not have a proper sources. Unable to continue.") if @sources.nil? if Contrast::Utils::DuckUtils.empty_duck?(signature) raise(ArgumentError, "#{ self } did not have a proper signature. Unable to continue.") end if Contrast::Utils::DuckUtils.empty_duck?(url) raise(ArgumentError, "#{ self } did not have a proper url. Unable to continue.") end nil end end end end end