# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true module Contrast module Agent # Scope lets us disable Contrast for certain code calls. We need to do this so # that we don't propagate through our own code. # # Think logging: If you have # something like "The source was '" + source + "'", and source is tracked, # you'll trigger propagation with the + method. This in turn would cause # propagation if you log there "The target ''" + target + "' was propagated'" # Which would then cause another propagation with the '+' method, forever. # # Instead, we should say "If I'm already doing Contrast things, don't track # this" class Scope # The following %i[] list is the authoritative list # of scopes. If you define a new symbol here, you'll # get scope methods: # %i[monkey] -> # enter_monkey_scope! # exit_monkey_scope! # in_monkey_scope? # with_monkey_scope { special_monkey_function } SCOPE_LIST = %i[contrast deserialization].cs__freeze iv_list = SCOPE_LIST.map { |name| :"@#{ name }_scope" } define_method 'initialize' do iv_list.each do |iv_sym| instance_variable_set(iv_sym, 0) end end SCOPE_LIST.each do |name| iv_sym = :"@#{ name }_scope" define_method "in_#{ name }_scope?" do instance_variable_get(iv_sym).positive? end enter_method_sym = :"enter_#{ name }_scope!" define_method enter_method_sym do level = instance_variable_get(iv_sym) instance_variable_set(iv_sym, level + 1) end exit_method_sym = :"exit_#{ name }_scope!" define_method exit_method_sym do # by design, can go below zero. # every exit/enter pair (regardless of series) # should cancel each other out. # # so we prefer this sequence: # scope = 0 # exit = -1 # enter = 0 # enter = 1 # exit = 0 # scope = 0 # # over this sequence: # scope = 0 # exit = 0 # enter = 1 # enter = 2 # exit = 1 # scope = 1 level = instance_variable_get(iv_sym) instance_variable_set(iv_sym, level - 1) end define_method "with_#{ name }_scope" do |*_args, &block| send enter_method_sym block.call ensure send exit_method_sym end end # Dynamic versions of the above. # These are equivalent, but they're slower and riskier. # Prefer the static methods if you know what scope you need at the call site. def in_scope? name cs__class.ensure_valid_scope! name call = with_contrast_scope { :"in_#{ name }_scope?" } send(call) end def enter_scope! name cs__class.ensure_valid_scope! name call = with_contrast_scope { :"enter_#{ name }_scope!" } send(call) end def exit_scope! name cs__class.ensure_valid_scope! name call = with_contrast_scope { :"exit_#{ name }_scope!" } send(call) end class << self def valid_scope? scope_sym Contrast::Agent::Scope::SCOPE_LIST.include? scope_sym end def ensure_valid_scope! scope_sym unless valid_scope? scope_sym # rubocop:disable Style/GuardClause with_contrast_scope do raise NoMethodError, "Scope '#{ scope_sym.inspect }' is not registered as a scope." end end end end end end end