# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'json' require 'contrast/components/logger' require 'contrast/agent/reporting/reporting_events/reporting_event' require 'contrast/utils/object_share' module Contrast module Agent module Reporting # This is the new Route Coverage 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 RouteCoverage # @param [String] the method signature used to uniquely identify the coverage report. attr_accessor :route # @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 [Integer] the number of times this was hit; no longer needed as it can be derived. attr_reader :count def initialize @route = Contrast::Utils::ObjectShare::EMPTY_STRING @verb = Contrast::Utils::ObjectShare::EMPTY_STRING @url = Contrast::Utils::ObjectShare::EMPTY_STRING @count = 0 end # Parse the given controller and route from a Rack based application framework in order to create an instance # of this class # # @param final_controller [Grape::API, Sinatra::Base] the controller responsible for the definition of the # entrypoint of the route actively being executed # @param method [String] the HTTP request method of the route actively being executed # @param route_pattern [Grape::Router::Route, Mustermann::Sinatra] the pattern to which the url maps # @param url [String] the literal url of the route actively being executed # @return [Contrast::Agent::Reporting::RouteCoverage] def attach_rack_based_data final_controller, method, route_pattern, url = nil if route_pattern.cs__is_a?(Grape::Router::Route) safe_pattern = route_pattern.pattern&.path&.to_s safe_url = source_or_string(url || safe_pattern) else safe_pattern = source_or_string(route_pattern) safe_url = source_or_string(url || route_pattern) end self.route = "#{ final_controller }##{ method } #{ safe_pattern }" self.verb = Contrast::Utils::StringUtils.force_utf8(method) self.url = Contrast::Utils::StringUtils.force_utf8(safe_url) self end # Convert ActionDispatch::Journey::Route to Contrast::Agent::Reporting::RouteCoverage # # @param journey_obj [ActionDispatch::Journey::Route] a rails route # @param url [String, nil] use url from string instead of journey object. # @return [Contrast::Agent::Reporting::RouteCoverage] def attach_rails_data journey_obj, url = nil self.route = "#{ journey_obj.defaults[:controller] }##{ journey_obj.defaults[:action] }" verb = source_or_string(journey_obj.verb) self.verb = Contrast::Utils::StringUtils.force_utf8(verb) url ||= source_or_string(journey_obj.path.spec) self.url = Contrast::Utils::StringUtils.force_utf8(url) self end def source_or_string obj if obj.cs__is_a?(Regexp) obj.source elsif obj.cs__respond_to?(:safe_string) obj.safe_string else obj.to_s end end end end end end