# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'contrast/utils/env_configuration_item' require 'ougai' require 'contrast/configuration' module Contrast module Components module Config # This component encapsulates storing the source for each entry in the config, # so that we can report on where the value was set from. class Sources ENVIRONMENT = 'ENV' CLI = 'CLI' CONTRASTUI = 'ContrastUI' DEFAULT = 'Default' YAML = 'YAML' # @return [Hash] attr_reader :data def initialize data = {} @data = data end # Retrieves the current config source for the specified config path. If no source is # set then returns the Default value. # # @param path [String] the canonical name for the config entry (such as api.proxy.enable) # @return [String] the source for the entry def get path data.dig(*parts_for(path)) || DEFAULT rescue TypeError DEFAULT end # Assigns the config source for a specified config path. # # @param path [String] the canonical name for the config entry (such as api.proxy.enable) # @param [String] the source for the entry # @return [String] the source type for the entry def set path, source assign_value(data, parts_for(path), source) end # Finds entries within config source data for the specified type, and returns # them along with the current values for each. # # @param type [String] a source type (ENV, CLI, ContrastUI, YAML) # @return [Hash] the entries for the provided source, along with the associated values def for type deep_select(data.dup, type, []) end private # @param path [String] the canonical name for a config entry (such as api.proxy.enable) # @return [Array] the path split on periods, and converted to symbols def parts_for path path.split('.').map(&:to_sym) end # @param current_level [Hash] all, or some of, the config source information # @param parts [Array] the parts for canonical name of the config entry # @param source [String] the source to be set for the specified entry # @return [Array] the path split on periods, and converted to symbols def assign_value current_level, parts, source parts[0...-1].each do |segment| current_level[segment] ||= {} current_level = current_level[segment] end return unless current_level.cs__is_a?(Hash) current_level[parts[-1]] = source end # @param sources [Hash] all, or some of, the config source information # @param type [Array] the source type to look for entries of # @param path [String] the entries followed to get to this part of the config # @return [Hash] the entries for the provided source, along with the associated values def deep_select sources, type, path sources.each_with_object({}) do |(k, v), grouping| if v.cs__is_a?(Hash) nested_data = deep_select(v, type, path.dup.append(k.to_sym)) grouping[k] = nested_data unless nested_data.empty? elsif v == type grouping[k] = Contrast::CONFIG.config.loaded_config.dig(*path, k) end end end end end end end