# This file is distributed under New Relic's license terms.
# See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
# frozen_string_literal: true

require 'new_relic/agent/configuration/dotted_hash'

module NewRelic
  module Agent
    module Configuration
      # The Language Security Policy Source gives customers the ability to
      # configure high security mode settings.
      class SecurityPolicySource < DottedHash
        class << self
          def enabled?(option)
            Agent.config[option]
          end

          def record_sql_enabled?(option)
            Agent.config[option] == 'obfuscated' ||
              Agent.config[option] == 'raw' ||
              false
          end

          def not_empty?(option)
            !Agent.config[option].empty?
          end

          def change_setting(policies, option, new_value)
            current_value = Agent.config[option]
            unless current_value == new_value
              NewRelic::Agent.logger.info( \
                "Setting changed: {#{option}: from #{current_value} " \
                "to #{new_value}}. Source: SecurityPolicySource"
              )
            end
            policies[option] = new_value
          end
        end

        # The keys of the security settings map are the names of security
        # policies received from the server. They map to multiple configuration
        # options in the local config. There is a hash of metadata that
        # corresponds to each configuration option with the following keys:
        #
        # option: the configuration option name
        # supported: true if the agent has one or more corresponding
        #   configuration options
        # enabled_fn: a callable that takes the configuration option and returns
        #   true if the option is enabled, false otherwise
        # disabled_value: the value of the configuration option when it is
        #   disabled
        # permitted_fn: a callable, that will be executed if an option is
        #   permitted by the security policy and is also enabled by the config

        SECURITY_SETTINGS_MAP = {
          'record_sql' => [
            {
              option: :'transaction_tracer.record_sql',
              supported: true,
              enabled_fn: method(:record_sql_enabled?),
              disabled_value: 'off',
              permitted_fn: proc { |policies|
                change_setting(policies, :'transaction_tracer.record_sql', 'obfuscated')
              }
            },
            {
              option: :'slow_sql.record_sql',
              supported: true,
              enabled_fn: method(:record_sql_enabled?),
              disabled_value: 'off',
              permitted_fn: proc { |policies|
                change_setting(policies, :'slow_sql.record_sql', 'obfuscated')
              }
            },
            {
              option: :'mongo.capture_queries',
              supported: true,
              enabled_fn: method(:enabled?),
              disabled_value: false,
              permitted_fn: proc { |policies|
                change_setting(policies, :'mongo.obfuscate_queries', true)
              }
            },
            {
              option: :'elasticsearch.capture_queries',
              supported: true,
              enabled_fn: method(:enabled?),
              disabled_value: false,
              permitted_fn: proc { |policies|
                change_setting(policies, :'elasticsearch.obfuscate_queries', true)
              }
            },
            {
              option: :'transaction_tracer.record_redis_arguments',
              supported: true,
              enabled_fn: method(:enabled?),
              disabled_value: false,
              permitted_fn: nil
            }
          ],
          'attributes_include' => [
            {
              option: :'attributes.include',
              supported: true,
              enabled_fn: method(:not_empty?),
              disabled_value: [],
              permitted_fn: nil
            },
            {
              option: :'transaction_tracer.attributes.include',
              supported: true,
              enabled_fn: method(:not_empty?),
              disabled_value: [],
              permitted_fn: nil
            },
            {
              option: :'transaction_events.attributes.include',
              supported: true,
              enabled_fn: method(:not_empty?),
              disabled_value: [],
              permitted_fn: nil
            },
            {
              option: :'error_collector.attributes.include',
              supported: true,
              enabled_fn: method(:not_empty?),
              disabled_value: [],
              permitted_fn: nil
            },
            {
              option: :'browser_monitoring.attributes.include',
              supported: true,
              enabled_fn: method(:not_empty?),
              disabled_value: [],
              permitted_fn: nil
            },
            {
              option: :'span_events.attributes.include',
              supported: true,
              enabled_fn: method(:not_empty?),
              disabled_value: [],
              permitted_fn: nil
            },
            {
              option: :'transaction_segments.attributes.include',
              supported: true,
              enabled_fn: method(:not_empty?),
              disabled_value: [],
              permitted_fn: nil
            }
          ],
          'ai_monitoring' => [
            {
              option: :'ai_monitoring.enabled',
              supported: true,
              enabled_fn: method(:enabled?),
              disabled_value: false,
              permitted_fn: nil
            }
          ],
          'allow_raw_exception_messages' => [
            {
              option: :'strip_exception_messages.enabled',
              supported: true,
              enabled_fn: method(:enabled?),
              disabled_value: false,
              permitted_fn: nil
            }
          ],
          'custom_events' => [
            {
              option: :'custom_insights_events.enabled',
              supported: true,
              enabled_fn: method(:enabled?),
              disabled_value: false,
              permitted_fn: nil
            }
          ],
          'custom_parameters' => [
            {
              option: :'custom_attributes.enabled',
              supported: true,
              enabled_fn: method(:enabled?),
              disabled_value: false,
              permitted_fn: nil
            }
          ],
          'custom_instrumentation_editor' => [
            {
              option: nil,
              supported: false,
              enabled_fn: nil,
              disabled_value: nil,
              permitted_fn: nil
            }
          ],
          'message_parameters' => [
            {
              option: :'message_tracer.segment_parameters.enabled',
              supported: true,
              enabled_fn: method(:enabled?),
              disabled_value: false,
              permitted_fn: nil
            }
          ]
        }

        def initialize(security_policies)
          super(build_overrides(security_policies))
        end

        ENABLED = 'enabled'.freeze
        COLON_COLON = '::'.freeze

        def build_overrides(security_policies)
          security_policies.inject({}) do |settings, (policy_name, policy_settings)|
            SECURITY_SETTINGS_MAP[policy_name].each do |policy|
              next unless policy[:supported]

              if policy_settings[ENABLED]
                if policy[:enabled_fn].call(policy[:option])
                  if permitted_fn = policy[:permitted_fn]
                    permitted_fn.call(settings)
                  end
                else
                  config_source = Agent.config.source(policy[:option]).class.name.split(COLON_COLON).last
                  NewRelic::Agent.logger.info( \
                    "Setting applied: {#{policy[:option]}: #{policy[:disabled_value]}}. " \
                    "Source: #{config_source}"
                  )
                end
              else
                settings[policy[:option]] = policy[:disabled_value]
                NewRelic::Agent.logger.info( \
                  "Setting applied: {#{policy[:option]}: #{policy[:disabled_value]}}. " \
                  'Source: SecurityPolicySource'
                )
              end
            end
            settings
          end
        end
      end
    end
  end
end
