# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true cs__scoped_require 'contrast/agent/patching/policy/method_policy' module Contrast module Agent module Patching module Policy # This class is used to map a class to all policy nodes utilizing that # class. It should be initialized using the create_module_policy method # rather than new. class ModulePolicy class << self # Given the name of a module, create a :ModulePolicy for it using # the Policy of each supported feature # # @param module_name [String] the name of the module to which the # policy applies # @return [Contrast::Agent::Patching::Policy::ModulePolicy] def create_module_policy module_name module_policy = Contrast::Agent::Patching::Policy::ModulePolicy.new module_policy.source_nodes = nodes_for_module(Contrast::Agent::Assess::Policy::Policy.instance.sources, module_name) module_policy.propagator_nodes = nodes_for_module(Contrast::Agent::Assess::Policy::Policy.instance.propagators, module_name) module_policy.trigger_nodes = nodes_for_module(Contrast::Agent::Assess::Policy::Policy.instance.triggers, module_name) module_policy.protect_nodes = nodes_for_module(Contrast::Agent::Protect::Policy::Policy.instance.triggers, module_name) module_policy.inventory_nodes = nodes_for_module(Contrast::Agent::Inventory::Policy::Policy.instance.triggers, module_name) module_policy.deadzone_nodes = nodes_for_module(Contrast::Agent::Deadzone::Policy::Policy.instance.deadzones, module_name) module_policy end # Find any of the given patchers that match this class' names. # Always returns an array, even if it's empty. # # @param nodes [Array(Contrast::Agent::Patching::Policy::PolicyNode)] # an array of nodes # @param class_name [String] the class to filter the nodes on # @return [Array] # Subset of nodes which apply to the given class def nodes_for_module nodes, class_name nodes.select { |node| class_name == node.class_name } end end attr_accessor :source_nodes, :propagator_nodes, :trigger_nodes, :inventory_nodes, :protect_nodes, :deadzone_nodes def empty? return false if source_nodes.any? return false if propagator_nodes.any? return false if trigger_nodes.any? return false if inventory_nodes.any? return false if protect_nodes.any? return false if deadzone_nodes.any? true end # The number of expected patches for this policy is the sum of unique # targeted methods for the Module to which this policy applies. # # @return [Integer] count of methods to be patched def num_expected_patches @_num_expected_patches ||= begin instance_methods = Set.new singleton_methods = Set.new sort_method_names(source_nodes, instance_methods, singleton_methods) sort_method_names(propagator_nodes, instance_methods, singleton_methods) sort_method_names(trigger_nodes, instance_methods, singleton_methods) sort_method_names(inventory_nodes, instance_methods, singleton_methods) sort_method_names(protect_nodes, instance_methods, singleton_methods) sort_method_names(deadzone_nodes, instance_methods, singleton_methods) instance_methods.length + singleton_methods.length end end def collect_class_nodes nodes, clazz_name return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless nodes nodes.select { |policy_node| clazz_name == policy_node.class_name } end def collect_method_nodes nodes, method, instance_method return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless nodes nodes.select do |node| node.instance_method? == instance_method && node.method_name == method end end def create_method_policy_subset method, instance_method method_policy = ModulePolicy.new method_policy.source_nodes = collect_method_nodes(source_nodes, method, instance_method) method_policy.propagator_nodes = collect_method_nodes(propagator_nodes, method, instance_method) method_policy.trigger_nodes = collect_method_nodes(trigger_nodes, method, instance_method) method_policy.inventory_nodes = collect_method_nodes(inventory_nodes, method, instance_method) method_policy.protect_nodes = collect_method_nodes(protect_nodes, method, instance_method) method_policy.deadzone_nodes = collect_method_nodes(deadzone_nodes, method, instance_method) method_policy end private def sort_method_names nodes, instance_methods, singleton_methods nodes.each do |node| if node.instance_method? instance_methods << node.method_name else singleton_methods << node.method_name end end end end end end end end