# 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/agent/protect/rule/sql_sample_builder'

module Contrast
  module Agent
    module Protect
      module Rule
        # The Ruby implementation of the Protect NoSQL Injection rule.
        class NoSqli < Contrast::Agent::Protect::Rule::BaseService
          # Generate a sample for the No-SQL injection detection rule, allowing for reporting to and rendering
          # by TeamServer
          include SqlSampleBuilder::NoSqliSample
          # Defining build_attack_with_match method
          include SqlSampleBuilder::AttackBuilder

          NAME = 'nosql-injection'
          BLOCK_MESSAGE = 'NoSQLi rule triggered. Response blocked.'
          class << self
            # @param attack_sample [Contrast::Api::Dtm::RaspRuleSample]
            # @return [Hash] the details for this specific rule
            def extract_details attack_sample
              {
                  start: attack_sample.no_sqli.start_idx,
                  end: attack_sample.no_sqli.end_idx,
                  boundaryOverrunIndex: attack_sample.no_sqli.boundary_overrun_idx,
                  inputBoundaryIndex: attack_sample.no_sqli.input_boundary_idx,
                  query: attack_sample.no_sqli.query
              }
            end
          end

          def rule_name
            NAME
          end

          def block_message
            BLOCK_MESSAGE
          end

          # @raise [Contrast::SecurityException] if the attack is blocked
          #   raised with BLOCK_MESSAGE
          def infilter context, database, query_string
            return unless infilter?(context)

            result = find_attacker(context, query_string, database: database)
            return unless result

            append_to_activity(context, result)

            cef_logging(result, :successful_attack)
            raise(Contrast::SecurityException.new(self, BLOCK_MESSAGE)) if blocked?
          end

          def build_attack_with_match context, input_analysis_result, result, candidate_string, **kwargs
            if mode == Contrast::Api::Settings::ProtectionRule::Mode::NO_ACTION ||
                  mode == Contrast::Api::Settings::ProtectionRule::Mode::PERMIT

              return result
            end

            result ||= build_attack_result(context)
            update_successful_attack_response(context, input_analysis_result, result, candidate_string)
            append_sample(context, input_analysis_result, result, candidate_string, **kwargs)
            result
          end

          protected

          def find_attacker context, potential_attack_string, **kwargs
            if potential_attack_string
              # We need the query hash to be a JSON string to match on JSON input attacks
              begin
                potential_attack_string = JSON.generate(potential_attack_string).to_s
              rescue JSON::GeneratorError
                logger.trace('Error in JSON::generate', input: potential_attack_string)
                nil
              end
            end
            super(context, potential_attack_string, **kwargs)
          end
        end
      end
    end
  end
end