# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
# frozen_string_literal: true

require 'digest'
require 'contrast/utils/hash_digest'

module Contrast
  module Utils
    # We use this class to provide hashes for our Request and Finding objects
    # based upon our definitions of uniqueness.
    # While the uniqueness of the request object is something internal to the
    # Ruby agent, the uniqueness of the Finding hash is defined by a
    # specification shared across all agent teams. The spec can be found here:
    # https://bitbucket.org/contrastsecurity/assess-specifications/src/master/vulnerability/preflight.md
    module HashDigestExtend
      # Generates the hash checksum for the request. Converts the request method, uri,
      # param names and content length to CRC checksum and returns string representation
      #
      # @param request [Contrast::Agent::Request] our wrapper around the Rack::Request.
      # @return checksum [String] String representation of CRC32 checksum
      def generate_request_hash request
        hash = new
        hash.update(request.request_method)
        hash.update(request.normalized_uri)
        request.parameters.each_key do |name|
          hash.update(name)
        end
        cl = request.headers[Contrast::Utils::HashDigest::CONTENT_LENGTH_HEADER]
        hash.update_on_content_length(cl) if cl
        hash.finish
      end

      # Generates the hash checksum for the event, either dataflow,
      # crypto(crypto-bad-ciphers, crypto-bad-mac) rules or trigger event
      # and returns string representation.
      #
      # @param finding [Contrast::Api::Dtm::Finding] to be reported
      # @param source [Object] the source of the Trigger Event
      # @param request [Contrast::Agent::Request] our wrapper around the Rack::Request.
      # @return checksum [String] String representation of CRC32 checksum
      def generate_event_hash finding, source, request
        return generate_dataflow_hash(finding, request) if finding.events.length.to_i > 1

        id = finding.rule_id
        return generate_crypto_hash(finding, source, request) if Contrast::Utils::HashDigest::CRYPTO_RULES.include?(id)

        generate_trigger_hash(finding, request)
      end

      # Generates the hash checksum for configurations. Converts the finding rule_id, session_id and configPath and
      # to CRC32 checksum and returns string representation to be appended to Contrast::Api::Dtm::Finding
      #
      # @param finding [Contrast::Api::Dtm::Finding] to be reported
      # @return checksum [String] String representation of CRC32 checksum.
      def generate_config_hash finding
        hash = new
        hash.update(finding.rule_id)
        path = finding.properties[Contrast::Utils::HashDigest::CONFIG_PATH_KEY]
        hash.update(path)
        method = finding.properties[Contrast::Utils::HashDigest::CONFIG_SESSION_ID_KEY]
        hash.update(method)
        hash.finish
      end

      # Generates the hash checksum for class scanning. Converts the rule_id, finding.properties(source, name)
      # to CRC32 checksum and returns string representation.
      #
      # @param finding [Contrast::Api::Dtm::Finding] to be reported
      # @return checksum [String] String representation of CRC32 checksum.
      def generate_class_scanning_hash finding
        hash = new
        hash.update(finding.rule_id)
        module_name = finding.properties[Contrast::Utils::HashDigest::CLASS_SOURCE_KEY]
        hash.update(module_name)
        # We're not currently collecting this. 30/7/19 HM
        line_no = finding.properties[Contrast::Utils::HashDigest::CLASS_LINE_NO_KEY]
        hash.update(line_no)
        field = finding.properties[Contrast::Utils::HashDigest::CLASS_CONSTANT_NAME_KEY]
        hash.update(field)
        hash.finish
      end

      private

      # Generates the hash checksum for crypto(crypto-bad-ciphers, crypto-bad-mac) rules.
      # used in #generate_event_hash.
      #
      #
      # @param finding [Contrast::Api::Dtm::Finding] to be reported
      # @param algorithm [Object] the source of the Trigger Event
      # @param request [Contrast::Agent::Request] our wrapper around the Rack::Request.
      # @return checksum [String] String representation of CRC32 checksum
      def generate_crypto_hash finding, algorithm, request
        hash = new
        hash.update(finding.rule_id)
        hash.update(algorithm)
        hash.update_on_request(finding, request)
        hash.finish
      end

      # Generates the hash checksum for dataflow when the finding events are more than one
      # used in #generate_event_hash.
      #
      # @param finding [Contrast::Api::Dtm::Finding] to be reported
      # @param request [Contrast::Agent::Request] our wrapper around the Rack::Request.
      # @return checksum [String] String representation of CRC32 checksum
      def generate_dataflow_hash finding, request
        hash = new
        hash.update(finding.rule_id)
        hash.update_on_sources(finding.events)
        hash.update_on_request(finding, request)
        hash.finish
      end

      # Generates the hash checksum for trigger
      # used in #generate_event_hash.
      #
      # @param finding [Contrast::Api::Dtm::Finding] to be reported
      # @param request [Contrast::Agent::Request] our wrapper around the Rack::Request.
      # @return checksum [String] String representation of CRC32 checksum
      def generate_trigger_hash finding, request
        hash = new
        hash.update(finding.rule_id)
        hash.update_on_request(finding, request)
        hash.finish
      end
    end
  end
end