# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true cs__scoped_require 'delegate' cs__scoped_require 'contrast/core_extensions/module' cs__scoped_require 'contrast/utils/boolean_util' module Contrast # This is the base module for our components classes. It is intended to # facilitate the translation of the Common Configuration settings to usable # Ruby methods. Any class under this namespace should be required here, # providing a single point of require for this functionality. module Components # Include this into your classes and modules, # and use 'access_component' to define constants that will allow # interaction with other components. module Interface def self.included klass # Upon inclusion, ComponentInterfaces extends the including with # these two interfaces. # Interface provides a class-level method 'access_component' # that regulates per-class access to agent state. # (It's a glorified `include MyComponent`). klass.extend Contrast::Components::ComponentReceiverClassInterface end end # All component access is gated through delegators. # # One delegator is used by the calling class, # so we can tweak outgoing calls. # # The second delegator is used by the receiving component, # so we can tweak incoming calls. # # We use __setobj__ to decide which component implementation to use. # This is intended to provide flexibility in design and # simplicity in testing. class ComponentDelegator < SimpleDelegator # intentionally left blank end # All components should inherit from this, # whether Interfaces, InstanceMethods or ClassMethods. module ComponentBase def self.included klass klass.extend Methods klass.include Methods end module Methods # :nodoc: def false? config_param Contrast::Utils::BooleanUtil.false?(config_param) end def true? config_param Contrast::Utils::BooleanUtil.true?(config_param) end # TODO: RUBY-536 private def _state_shim Contrast::Agent::FeatureState.instance end alias_method :state, :_state_shim end end def self.component_const_name mod_name mod_name = mod_name.split('::').last @cache ||= {} @cache[mod_name] ||= mod_name. # CamelCaseName split(/(?=[A-Z])/)&. # ['Camel', 'Case', 'Name'] map(&:upcase)&. # ['CAMEL', 'CASE', 'NAME'] join('_') # 'CAMEL_CASE_NAME' end # Interface to allow for iteration over each of the configuration # components module ComponentReceiverClassInterface # Components are manually required at the end of # this file, and this constant is then frozen. # RUBY-535 to handle this better. COMPONENT_MAP = {} # rubocop:disable Style/MutableConstant # TODO: RUBY-535 # This module is used via `extend`, so it can't access # constants we define here. def component_map COMPONENT_MAP end # .access_component # # to be used as: # # class Abc # include Contrast::Components::Interface # access_component :logger, :agent # # def function # if AGENT.disabled? # 0 / 3 # end # rescue # logger.error "this function did error" # end # end # # `:logger` creates a #logger and .logger method # `:agent` provides an AGENT constant, analogous to a local singleton. # def access_component *component_set_syms @_access_component ||= {} component_set_syms.each do |sym| next if @_access_component[sym] if (mods = component_map[sym]) # rubocop:disable Style/GuardClause # We may support multiple components via one access request. mods.each do |m| name = Contrast::Components.component_const_name(m.name) cs__const_set(name, m::COMPONENT_INTERFACE) if m.cs__const_defined?(:COMPONENT_INTERFACE) include m::InstanceMethods if m.cs__const_defined?(:InstanceMethods, false) extend m::ClassMethods if m.cs__const_defined?(:ClassMethods, false) end @_access_component[sym] = true else raise NotImplementedError, "#{ self } asked to access undefined component '#{ sym }'." end end end end end end # Components can depend on other components, but it should be a # directed acyclic graph. # Scope shouldn't depend on anything. cs__scoped_require 'contrast/components/scope' Contrast::Components::ComponentReceiverClassInterface::COMPONENT_MAP[:scope] = [Contrast::Components::Scope] # Config depends on Scope. cs__scoped_require 'contrast/components/config' Contrast::Components::ComponentReceiverClassInterface::COMPONENT_MAP[:config] = [Contrast::Components::Config] # Settings should not depend on anything but Config. cs__scoped_require 'contrast/components/settings' Contrast::Components::ComponentReceiverClassInterface::COMPONENT_MAP[:settings] = [Contrast::Components::Settings] cs__scoped_require 'contrast/components/agent' Contrast::Components::ComponentReceiverClassInterface::COMPONENT_MAP[:agent] = [Contrast::Components::Agent] cs__scoped_require 'contrast/components/app_context' Contrast::Components::ComponentReceiverClassInterface::COMPONENT_MAP[:app_context] = [Contrast::Components::AppContext] cs__scoped_require 'contrast/components/contrast_service' Contrast::Components::ComponentReceiverClassInterface::COMPONENT_MAP[:contrast_service] = [Contrast::Components::ContrastService] cs__scoped_require 'contrast/components/heap_dump' Contrast::Components::ComponentReceiverClassInterface::COMPONENT_MAP[:heap_dump] = [Contrast::Components::HeapDump] cs__scoped_require 'contrast/components/logger' Contrast::Components::ComponentReceiverClassInterface::COMPONENT_MAP[:logging] = [Contrast::Components::Logger] cs__scoped_require 'contrast/components/assess' cs__scoped_require 'contrast/components/protect' cs__scoped_require 'contrast/components/inventory' Contrast::Components::ComponentReceiverClassInterface::COMPONENT_MAP[:analysis] = [Contrast::Components::Protect, Contrast::Components::Assess, Contrast::Components::Inventory] cs__scoped_require 'contrast/components/sampling' Contrast::Components::ComponentReceiverClassInterface::COMPONENT_MAP[:sampling] = [Contrast::Components::Sampling] Contrast::Components::ComponentReceiverClassInterface::COMPONENT_MAP.cs__freeze