# Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'contrast/utils/object_share' require 'contrast/config/diagnostics/effective_config_value' require 'contrast/config/diagnostics/singleton_tools' require 'contrast/utils/duck_utils' module Contrast module Config module Diagnostics # Diagnostics tools to be included in config components. module Tools CHECK = 'd' extend Contrast::Config::Diagnostics::SingletonTools # TODO: RUBY-2113 deprecate name_prefix # # Converts current configuration from array of values to effective config values class and appends them to # EffectiveConfig class. Must be used inside Config Components only. # # @param effective_config [Contrast::Config::Diagnostics::EffectiveConfig] # @param config_values [] array of the names of values. # @param canonical_prefix [String] starting of the path to config => api.proxy... # @param name_prefix [String] the name of the config prefix => contrast.api_key, contrast.url def add_effective_config_values(effective_config, config_values, canonical_prefix, name_prefix = canonical_prefix) return if config_values.to_s.empty? config_values.each do |config_value_name| Contrast::Config::Diagnostics::EffectiveConfigValue.new.tap do |new_effective_value| config_value = send(config_value_name.to_sym) fill_effective_value(new_effective_value, config_value, config_value_name, canonical_prefix, name_prefix) effective_config.values << new_effective_value rescue StandardError => e log_error(e) next end end end # TODO: RUBY-2113 deprecate name_prefix # # Converts current configuration from single value to effective config values class and appends them to # EffectiveConfig class. Must be used inside Config Components only. # # @param effective_config [Contrast::Config::Diagnostics::EffectiveConfig] # @param config_name [String] name of the config. # @param config_value [String, Boolean] value of the config. # @param canonical_prefix [String] starting of the path to config => api.proxy... # @param name_prefix [String] the name of the config prefix => contrast.api_key, contrast.url def add_single_effective_value(effective_config, config_name, config_value, canonical_prefix, name_prefix = canonical_prefix) Contrast::Config::Diagnostics::EffectiveConfigValue.new.tap do |new_effective_value| fill_effective_value(new_effective_value, config_value, config_name, canonical_prefix, name_prefix) effective_config.values << new_effective_value rescue StandardError => e log_error(e) next end end private # Fills instance of effective configuration value from read configuration. # # @param new_effective_value [Contrast::Config::Diagnostics::EffectiveConfigValue] # @param config_value [String, Boolean] value of the config # @param config_value_name [string] name of the value e.g. enabled? # @param canonical_prefix [String] starting of the path to config => api.proxy... # @param name_prefix [String] the name of the config prefix => contrast.api_key, contrast.url # @return filled_new_effective_config [Contrast::Config::Diagnostics::EffectiveConfigValue] def fill_effective_value new_effective_value, config_value, config_value_name, canonical_prefix, name_prefix find_source(new_effective_value, canonical_prefix, assign_name(config_value_name), name_prefix) if Contrast::Config::Diagnostics::SingletonTools::API_CREDENTIALS.include?(config_value_name.to_s) new_effective_value.value = Contrast::Configuration::EFFECTIVE_REDACTED return new_effective_value end new_effective_value.value = config_value.cs__is_a?(Array) ? config_value.join(',') : config_value.to_s new_effective_value end # Assigns a proper name for the config removing '?' out of method names. # # @param config [String] name of the configuration # @return [String] def assign_name config return Contrast::Utils::ObjectShare::EMPTY_STRING unless config name = config.dup if name.end_with?(Contrast::Utils::ObjectShare::QUESTION_MARK) # check and remove '?' : start_bundled_service? => start_bundled_service name.delete!(Contrast::Utils::ObjectShare::QUESTION_MARK) # converts name e.g. enabled => enable name.chop! if name.end_with?(CHECK) name end name end # Retrieves the config value from sources by canonical name and sets the key value. # # @param new_effective_value [Contrast::Config::Diagnostics::EffectiveConfigValue] # @param canonical_prefix [String] starting of the path to config => api.proxy... # @param config_name [String] name of the config. # @param name_prefix [String] the name of the config prefix => contrast.api_key, contrast.url # @return [Contrast::Config::Diagnostics::EffectiveConfigValue] def find_source new_effective_value, canonical_prefix, config_name, name_prefix new_effective_value.key = "#{ name_prefix }.#{ config_name }" new_effective_value.canonical_name = "#{ canonical_prefix }.#{ config_name }" # For files we keep the whole path as source. source = Contrast::CONFIG.sources.get(new_effective_value.canonical_name) new_effective_value.assign_filename(source) new_source = if Contrast::CONFIG.sources.configuration_file_source?(new_effective_value.canonical_name) Contrast::Components::Config::Sources::APP_CONFIGURATION_FILE end new_effective_value.source = new_source || source new_effective_value end # Logs any caught error. # # @param error [StandardError] def log_error error Contrast::CONFIG.proto_logger.warn(Contrast::Config::Diagnostics::Monitor::ERROR_MESSAGE, error: error, backtrace: error.backtrace) end end end end end