# Copyright (c) 2022 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/utils/timer' require 'contrast/agent/request' require 'contrast/agent/response' require 'contrast/agent/inventory/database_config' require 'contrast/components/logger' require 'contrast/components/scope' require 'contrast/utils/request_utils' require 'contrast/agent/request_context_extend' require 'contrast/agent/reporting/reporting_events/observed_route' require 'contrast/agent/reporting/input_analysis/input_analysis' module Contrast module Agent # This class acts to encapsulate information about the currently executed request, making it available to the Agent # for the duration of the request in a standardized and normalized format which the Agent understands. class RequestContext include Contrast::Components::Logger::InstanceMethods include Contrast::Components::Scope::InstanceMethods include Contrast::Utils::RequestUtils include Contrast::Agent::RequestContextExtend EMPTY_INPUT_ANALYSIS_PB = Contrast::Api::Settings::InputAnalysis.new # @return [Contrast::Api::Dtm::Activity] the application activity found in this request attr_reader :activity # @return [Hash] context used to log the request attr_reader :logging_hash # @return [Contrast::Agent::Reporting::ObservedRoute] the route, used for coverage, of this request attr_reader :new_observed_route # @return [Contrast::Api::Dtm::ObservedRoute] the route, used for coverage, of this request attr_reader :observed_route # @return [Contrast::Agent::Request] our wrapper around the Rack::Request for this context attr_reader :request # @return [Contrast::Agent::Response] our wrapper around the Rack::Response or Array for this context, # only available after the application has finished its processing attr_reader :response # @return [Contrast::Api::Dtm::RouteCoverage] the route, used for findings, of this request attr_reader :route # @return [Contrast::Api::Dtm::ServerActivity] the server activity found in this request attr_reader :server_activity # @return [Contrast::Api::Settings::InputAnalysis] the protect input analysis of sources on this request attr_reader :speedracer_input_analysis # @return [Contrast::Utils::Timer] when the context was created attr_reader :timer def initialize rack_request, app_loaded: true with_contrast_scope do # all requests get a timer and hash @timer = Contrast::Utils::Timer.new @logging_hash = { request_id: __id__ } # instantiate helper for request and response @request = Contrast::Agent::Request.new(rack_request) @activity = Contrast::Api::Dtm::Activity.new @activity.http_request = request.dtm @server_activity = Contrast::Api::Dtm::ServerActivity.new # build analyzer @do_not_track = false @speedracer_input_analysis = EMPTY_INPUT_ANALYSIS_PB speedracer_input_analysis.request = request # TODO: RUBY-1627 # flag to indicate whether the app is fully loaded @app_loaded = !!app_loaded # generic holder for properties that can be set throughout this request @_properties = {} if ::Contrast::ASSESS.enabled? @sample_req, @sample_res = Contrast::Utils::Assess::SamplingUtil.instance.sample?(@request) end handle_routes end end def app_loaded? @app_loaded end def analyze_request? analyze_request_assess? || analyze_req_res_protect? end def analyze_response? analyze_response_assess? || analyze_req_res_protect? end def analyze_req_res_protect? ::Contrast::PROTECT.enabled? end def analyze_request_assess? return false unless analyze_req_res_assess? @sample_req end def analyze_response_assess? return false unless analyze_req_res_assess? @sample_res &&= ::Contrast::ASSESS.scan_response? end def analyze_req_res_assess? ::Contrast::ASSESS.enabled? end def add_property key, value @_properties[key] = value end def get_property key @_properties[key] end def reset_activity @activity = Contrast::Api::Dtm::Activity.new(http_request: request.dtm) @server_activity = Contrast::Api::Dtm::ServerActivity.new @observed_route = Contrast::Api::Dtm::ObservedRoute.new # TODO: RUBY-1438 -- remove @new_observed_route = Contrast::Agent::Reporting::ObservedRoute.new end private def handle_routes @observed_route = Contrast::Api::Dtm::ObservedRoute.new # TODO: RUBY-1438 -- remove @new_observed_route = Contrast::Agent::Reporting::ObservedRoute.new route_dtm = Contrast::Agent.framework_manager.get_route_dtm(@request) new_route_coverage_dtm = Contrast::Agent.framework_manager.get_route_information(@request) append_route_coverage(route_dtm) append_to_new_observed_route(new_route_coverage_dtm) end end end end