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

require 'json'
require 'contrast/agent/assess/rule/provider/hardcoded_value_rule'
require 'contrast/agent/assess/rule/provider/hardcoded_key'
require 'contrast/agent/assess/rule/provider/hardcoded_password'
require 'contrast/agent/assess/policy/policy_node'
require 'contrast/agent/assess/policy/source_node'
require 'contrast/agent/assess/policy/propagation_node'
require 'contrast/agent/assess/policy/trigger_node'
require 'contrast/agent/patching/policy/policy'

module Contrast
  module Agent
    module Assess
      module Policy
        # This is just a holder for our policy. Takes the policy JSON and
        # converts it into hashes that we can access nicely
        class Policy < Contrast::Agent::Patching::Policy::Policy
          # Indicates the folder in `resources` where this policy lives.
          def self.policy_folder
            'assess'
          end

          # Indicates is this feature has been disabled by the configuration,
          # read at startup, and therefore can never be enabled.
          def disabled_globally?
            ASSESS.forcibly_disabled?
          end

          def node_type
            Contrast::Agent::Assess::Policy::TriggerNode
          end

          def initialize
            super
            load_providers
          end

          # Our policy for dataflow rules is a 'dope ass' JSON file. Rather than
          # hard code in a bunch of things to monkey patch, we let the JSON file
          # define the conditions in which sources, propagators, and triggers are
          # applied.
          # This let's us be flexible and extensible
          # * when we want to do lvl 2 rules, we could have the customers unzip
          # our gem, insert things into the json, zip, and go *
          def from_hash_string string
            # The default behavior of the agent is to load the policy on startup,
            # as at this point we do not know in which mode we'll be run.
            #
            # If the configuration file explicitly disables a feature, we know
            # that we will not ever be able to enable it, so in that case, we
            # can skip policy loading.
            return if disabled_globally?

            policy_data = JSON.parse(string)

            policy_data[SOURCES_KEY].each do |source_hash|
              source = Contrast::Agent::Assess::Policy::SourceNode.new(source_hash)
              add_node(source, :source)
            end

            policy_data[PROPAGATION_KEY].each do |propagator_hash|
              prop = Contrast::Agent::Assess::Policy::PropagationNode.new(propagator_hash)
              add_node(prop, :propagator)
            end

            policy_data[RULES_KEY].each do |rule_hash|
              rule_hash[TRIGGERS_KEY].each do |trigger_hash|
                trigger_node = node_type.new(trigger_hash, rule_hash)
                add_node(trigger_node)
              end
            end
          end

          # Providers is a term that we're taking from Java until we come up with
          # a name that we (I) don't hate. Basically, these are more static like
          # rules. They don't do dataflow or response scanning. Instead, they
          # watch for things to be loaded (configs, classes, whateves) and
          # determine if these loaded things are unsafe.
          #
          # ** if we want, we could add this as a section to the aforementioned
          # 'dope ass' JSON
          def load_providers
            PROVIDER_CLASSES.each do |clazz|
              instance = clazz.new
              providers[instance.rule_id] = instance
            end
          end

          PROVIDER_CLASSES = [
            Contrast::Agent::Assess::Rule::Provider::HardcodedKey,
            Contrast::Agent::Assess::Rule::Provider::HardcodedPassword
          ].cs__freeze
        end
      end
    end
  end
end