# 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'
require 'contrast/components/logger'

module Contrast
  module Agent
    module Protect
      module Rule
        # Encapsulate common code for protect rules that do their
        # input analysis on Speedracer rather in ruby code
        class BaseService < Contrast::Agent::Protect::Rule::Base
          include Contrast::Components::Logger::InstanceMethods

          def rule_name
            'base-service'
          end

          def block_message
            'Contrast Security Protect Rule Triggered. Response blocked.'
          end

          def infilter? context
            return false unless context&.speedracer_input_analysis&.results
            return false unless enabled?
            return false if protect_excluded_by_code?

            true
          end

          # Override for rules that need the response
          # Currently postfilter can be applied to streamed responses,
          # if any logic within postfilter changes to modify the response
          # streamed responses will break
          def postfilter context
            return unless enabled? && POSTFILTER_MODES.include?(mode)
            if mode == Contrast::Api::Settings::ProtectionRule::Mode::NO_ACTION ||
                  mode == Contrast::Api::Settings::ProtectionRule::Mode::PERMIT

              return
            end

            result = find_postfilter_attacker(context, nil)
            return unless result&.samples&.any?

            cef_logging result
            append_to_activity(context, result)
            return unless result.response == :BLOCKED

            raise Contrast::SecurityException.new(self, "#{ rule_name } triggered in postfilter. Response blocked.")
          end

          protected

          def gather_ia_results context
            context.speedracer_input_analysis.results.select do |ia_result|
              ia_result.rule_id == rule_name
            end
          end

          def find_attacker context, potential_attack_string, **kwargs
            ia_results = gather_ia_results(context)
            find_attacker_with_results(context, potential_attack_string, ia_results, **kwargs)
          end

          # Allows for the InputAnalysis from service to be extracted early
          def find_attacker_with_results context, potential_attack_string, ia_results, **kwargs
            logger.trace('Checking vectors for attacks', rule: rule_name, input: potential_attack_string)

            result = nil
            ia_results.each do |ia_result|
              if potential_attack_string
                idx = potential_attack_string.index(ia_result.value)
                next unless idx

                result = build_attack_with_match(context, ia_result, result, potential_attack_string, **kwargs)
              else
                result = build_attack_without_match(context, ia_result, result, **kwargs)
              end
            end
            result
          end

          private

          def find_postfilter_attacker context, potential_attack_string, **kwargs
            ia_results = gather_ia_results(context)
            ia_results.select! do |ia_result|
              ia_result.score_level == if ia_result.rule_id == Contrast::Agent::Protect::Rule::Sqli::NAME ||
                    ia_result.rule_id == Contrast::Agent::Protect::Rule::CmdInjection::NAME

                                         Contrast::Agent::Reporting::ScoreLevel::WORTHWATCHING
                                       else
                                         # legacy implementation for DEFINITEATATACK
                                         Contrast::Api::Settings::InputAnalysisResult::ScoreLevel::DEFINITEATTACK
                                       end
            end
            find_attacker_with_results(context, potential_attack_string, ia_results, **kwargs)
          end
        end
      end
    end
  end
end