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

require 'contrast/components/base'
require 'contrast/components/scope'

module Contrast
  module Components
    module Ruby
      # A wrapper build around the Common Agent Configuration project to allow
      # for access of the values contained in its parent_configuration_spec.yaml.
      # Those in this section pertain to the specific settings that apply to Ruby.
      class Interface
        include Contrast::Components::ComponentBase

        DISABLED_RAKE_TASK_LIST = %w[
          about assets:clean assets:clobber assets:environment
          assets:precompile assets:precompile:all db:create db:drop db:fixtures:load db:migrate
          db:migrate:status db:rollback db:schema:cache:clear db:schema:cache:dump db:schema:dump
          db:schema:load db:seed db:setup db:structure:dump db:version doc:app graphql:install graphql:object
          log:clear middleware notes notes:custom rails:template rails:update routes secret spec spec:features
          spec:requests spec:controllers spec:helpers spec:models spec:views spec:routing spec:rcov stats
          test test:all test:all:db test:recent test:single test:uncommitted time:zones:all tmp:clear
          tmp:create webpacker:compile
        ].cs__freeze

        DEFAULT_UNINSTRUMENTED_NAMESPACES = %w[FactoryGirl FactoryBot].cs__freeze
        CANON_NAME = 'agent.ruby'
        CONFIG_VALUES = %w[
          disabled_agent_rake_tasks
          propagate_yield require_scan
          non_request_tracking
          uninstrument_namespace
          application_scope
        ].cs__freeze

        # Set a comma-separated string of rake tasks in which to disable agent operation.
        #
        # @return [Array]
        attr_writer :disabled_agent_rake_tasks
        # @return [ExceptionConfiguration]
        attr_writer :exceptions
        # Controls whether or not we patch the rb_yield block to track split propagation
        #
        # @return [Boolean]
        attr_writer :propagate_yield
        # Controls the scan of the given trace_point, built from an :end event, to determine
        # where the loaded code lives and scanning that code for policy any violations.
        # @return [Boolean]
        attr_writer :require_scan
        # Controls whether the agent should track outside request
        #
        # @return [Boolean]
        attr_writer :non_request_tracking
        # listed module names for skipping instrumentation.
        #
        # @return [Array]
        attr_writer :uninstrument_namespace
        # Controls if the agent should instrument only the application code
        #
        # @return[Boolean]
        attr_accessor :application_scope

        # @return [String]
        attr_reader :canon_name
        # @return [Array]
        attr_reader :config_values

        def initialize hsh = {}
          @config_values = CONFIG_VALUES
          @canon_name = CANON_NAME
          return unless hsh

          @disabled_agent_rake_tasks = hsh[:disabled_agent_rake_tasks]
          @exceptions = Contrast::Config::ExceptionConfiguration.new(hsh[:exceptions])
          @propagate_yield = hsh[:propagate_yield]
          @require_scan = hsh[:require_scan]
          @non_request_tracking = hsh[:non_request_tracking]
          @uninstrument_namespace = hsh[:uninstrument_namespace]
          # This value must only be read form ENV. The cs__with_app_scope? method handles the
          # validation of the set Application Scope Flag.
          @application_scope = Contrast::Components::Scope.cs__with_app_scope? == 1
        end

        # These commands being detected will result the agent disabling instrumentation, generally any command
        # that doesn't result in the application listening on a port can be added here, this normally includes tasks
        # that are ran pre-startup(like migrations) or to show information about the application(such as routes)
        # @return [Array, DISABLED_RAKE_TASK_LIST]
        def disabled_agent_rake_tasks
          @disabled_agent_rake_tasks.nil? ? DISABLED_RAKE_TASK_LIST : @disabled_agent_rake_tasks
        end

        # rubocop:disable Naming/MemoizedInstanceVariableName
        # @return [Contrast::Config::ExceptionConfiguration]
        def exceptions
          @exceptions ||= Contrast::Config::ExceptionConfiguration.new
        end
        # rubocop:enable Naming/MemoizedInstanceVariableName

        # controls whether or not we patch the rb_yield block to track split propagation
        # @return [Boolean, Contrast::Utils::ObjectShare::TRUE]
        def propagate_yield
          @propagate_yield.nil? ? Contrast::Utils::ObjectShare::TRUE : @propagate_yield
        end

        # control whether or not we run file scanning rules on require
        # @return [Boolean, Contrast::Utils::ObjectShare::TRUE]
        def require_scan
          @require_scan.nil? ?  Contrast::Utils::ObjectShare::TRUE  : @require_scan
        end

        # controls tracking outside of request
        # @return [Boolean, Contrast::Utils::ObjectShare::FALSE]
        def non_request_tracking
          @non_request_tracking.nil? ? Contrast::Utils::ObjectShare::FALSE : @non_request_tracking
        end

        # @return [Array, DEFAULT_UNINSTRUMENTED_NAMESPACES]
        def uninstrument_namespace
          @uninstrument_namespace.nil? ? DEFAULT_UNINSTRUMENTED_NAMESPACES : @uninstrument_namespace
        end

        # Checks to see if we support with_app_scope for the current Agent Startup.
        # Defaults to false if configuration is not set.
        #
        # @return [Boolean]
        def start_with_application_scope?
          return application_scope if application_scope

          false
        end
      end
    end
  end
end