# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'contrast/components/scope' require 'contrast/utils/object_share' module Contrast module Agent module Patching module Policy # This class functions to translate our policy.json into an actionable # Ruby object, allowing for dynamic patching over hardcoded patching. # # @abstract class PolicyNode include Contrast::Components::Scope::InstanceMethods JSON_METHOD_VISIBILITY = 'method_visibility' JSON_PROPERTIES = 'properties' JSON_METHOD_NAME = 'method_name' JSON_METHOD_SCOPE = 'scope' # The keys used to read from policy.json to create the individual # policy nodes. These are common across node types JSON_CLASS_NAME = 'class_name' JSON_INSTANCE_METHOD = 'instance_method' def initialize policy_hash = {} @class_name = policy_hash[JSON_CLASS_NAME] @instance_method = policy_hash[JSON_INSTANCE_METHOD] @method_name = policy_hash[JSON_METHOD_NAME] @method_scope = policy_hash[JSON_METHOD_SCOPE] @method_visibility = policy_hash[JSON_METHOD_VISIBILITY] @properties = policy_hash[JSON_PROPERTIES] symbolize end # Name of the class in which the method is being invoked. attr_accessor :class_name # Check for instance method. # # @return true | false attr_accessor :instance_method # The symbol representation of the invoked method. attr_accessor :method_name # Visibility of the invoked method [Private, Public, Protected] attr_accessor :method_visibility # Properties parsed from our JSON policy. attr_reader :properties # Scope of the method parsed from our JSON policy. attr_reader :method_scope def node_class raise(NoMethodError, 'specify the type of the feature for which this node patches') end def feature raise(NoMethodError, 'specify the name of the feature for which this node patches') end def id @_id ||= "#{ feature }:#{ node_class }:#{ class_name }#{ instance_method? ? '#' : '.' }#{ method_name }" end # Don't let nodes be created that will be missing things we need # later on. Really, if they don't have these things, they couldn't have # done their jobs anyway. def validate unless class_name raise(ArgumentError, "#{ node_class } #{ id } did not have a proper class name. Unable to create.") end unless method_name raise(ArgumentError, "#{ node_class } #{ id } did not have a proper method name. Unable to create.") end unless method_name.is_a?(Symbol) raise(ArgumentError, "#{ node_class } #{ id } has a non symbol @method_name value. Unable to create.") end unless method_visibility.is_a?(Symbol) raise(ArgumentError, "#{ node_class } #{ id } has a non symbol @method_visibility value. Unable to create.") end unless method_scope.nil? || Contrast::Agent::Scope.valid_scope?(method_scope) raise(ArgumentError, "#{ node_class } #{ id } requires an undefined scope. Unable to create.") end nil end # just turns this into a ruby-ism def instance_method? instance_method end private # Convert strings to symbols here, once, to avoid doing so on every # comparison at runtime def symbolize @method_name = @method_name.to_sym if @method_name @method_visibility = @method_visibility.to_sym if @method_visibility @method_scope = @method_scope.to_sym if @method_scope end end end end end end