# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'contrast/agent/protect/rule/base_service' require 'contrast/components/logger' require 'contrast/agent/reporting/input_analysis/input_type' require 'contrast/agent/reporting/input_analysis/score_level' require 'contrast/agent_lib/interface' module Contrast module Agent module Protect module Rule # The Ruby implementation of the Protect BotBlocker rule. class BotBlocker < Contrast::Agent::Protect::Rule::BaseService include Contrast::Components::Logger::InstanceMethods include Contrast::Agent::Reporting::InputType NAME = 'bot-blocker' APPLICABLE_USER_INPUTS = [HEADER].cs__freeze def rule_name NAME end def applicable_user_inputs APPLICABLE_USER_INPUTS end # BotBlocker prefilter: # # @param context [Contrast::Agent::RequestContext] current request contest # @raise [Contrast::SecurityException] if the rule mode ise set # to BLOCK and valid bot is detected. def prefilter context return unless prefilter?(context) # We expect only one result per request since the user-agent Header is one. # And the IA analysis explicitly searches for the key match before starting # the analysis. return unless (ia_result = gather_ia_results(context)[0]) && ia_result.score_level == Contrast::Agent::Reporting::ScoreLevel::DEFINITEATTACK result = build_attack_without_match(context, ia_result, nil) append_to_activity(context, result) if result cef_logging(result, :successful_attack) if result return unless blocked? # Raise BotBlocker error exception_message = "#{ rule_name } rule triggered. Unsafe Bot blocked." raise(Contrast::SecurityException.new(self, exception_message)) end # @param context [Contrast::Agent::RequestContext] # @return [Array] def gather_ia_results context return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless context&.agent_input_analysis&.results context.agent_input_analysis.results.select do |ia_result| ia_result.rule_id == rule_name end end # Adding bot blocker details # # @param context [Contrast::Agent::RequestContext] # @param ia_result [Contrast::Agent::Reporting::InputAnalysisResult] # @param _candidate_string # @param **_kwargs # @return [Contrast::Api::Dtm::RaspRuleSample] def build_sample context, ia_result, _candidate_string, **_kwargs sample = build_base_sample(context, ia_result) sample.details = Contrast::Agent::Reporting::BotBlockerDetails.new sample.details.bot = ia_result.value sample.details.user_agent = context&.request&.user_agent sample end end end end end end