# Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'forwardable' require 'contrast/utils/object_share' module Contrast module Config # This is the base for our configuration classes. It is intended to # facilitate the translation of the Common Configuration settings to usable # Ruby classes. class BaseConfiguration extend Forwardable attr_reader :configuration_map alias_method :to_hash, :configuration_map def_delegators :@configuration_map, :empty?, :key?, :delete, :fetch, :[], :[]=, :each, :each_pair, :each_key, :each_value EMPTY_VALUE = :EMPTY_VALUE def initialize hsh = {}, keys = {} # holds configuration key value pairs # each configuration class can contain nested BaseConfigurations @configuration_map = {} traverse_config(hsh, keys) end def assign_value_to_path_array dot_path_array, value current_level = self dot_path_array[0...-1].each do |segment| current_level = current_level.send(segment) if current_level.cs__respond_to?(segment) end last_entry = dot_path_array[-1] if current_level.nil? == false && current_level.cs__respond_to?(last_entry) current_level.send("#{ last_entry }=", value) end nil end def nil? @configuration_map.empty? end private # Traverse the given entity to build out the configuration graph. # # The values will be either a hash, indicating internal nodes to # traverse, or a value to set or the EMPTY_VALUE symbol, indicating a # leaf node. # # The spec_keys are the Contrast defined keys used to access a given # configuration. Each child of this class maintains its own set of keys, # as well as Objects to which those keys map. def traverse_config values, spec_keys internal_nodes = values.cs__respond_to?(:has_key?) spec_keys.each_pair do |spec_key, spec_value| user_provided_value = internal_nodes ? value_from_key_config(spec_key, values) : EMPTY_VALUE define_methods(spec_key, spec_value, user_provided_value) end end def define_methods spec_key, spec_value, user_provided_value str_key = spec_key.to_s assign_config_value(str_key, spec_value, user_provided_value) define_getter(str_key) define_setter(str_key) end def assign_config_value str_key, spec_value, user_provided_value @configuration_map[str_key] = if spec_value.is_a?(Class) && spec_value <= Contrast::Config::BaseConfiguration spec_value.new(user_provided_value) elsif user_provided_value == EMPTY_VALUE spec_value else user_provided_value end end def value_from_key_config key, config_hash return config_hash[key] if config_hash.key?(key) return config_hash.fetch(key.to_sym, EMPTY_VALUE) if key.is_a?(String) config_hash.fetch(key.to_s, EMPTY_VALUE) end def define_getter str_key define_singleton_method str_key.to_sym do @configuration_map[str_key] == EMPTY_VALUE ? nil : @configuration_map[str_key] end end def define_setter str_key define_singleton_method "#{ str_key }=".to_sym do |new_value| @configuration_map[str_key] = new_value end end end end end