# Copyright (c) 2022 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 SCOPE_LIST = %i[contrast deserialization split].cs__freeze def initialize @contrast_scope = 0 @deserialization_scope = 0 @split_scope = 0 end def in_contrast_scope? @contrast_scope.positive? end def in_deserialization_scope? @deserialization_scope.positive? end def in_split_scope? @split_scope.positive? end def enter_contrast_scope! @contrast_scope += 1 end def enter_deserialization_scope! @deserialization_scope += 1 end def enter_split_scope! @split_scope += 1 end def split_scope_depth @split_scope end # Scope Exits... # 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 def exit_contrast_scope! @contrast_scope -= 1 end def exit_deserialization_scope! @deserialization_scope -= 1 end def exit_split_scope! @split_scope -= 1 end def with_contrast_scope enter_contrast_scope! yield ensure exit_contrast_scope! end def with_deserialization_scope enter_deserialization_scope! yield ensure exit_deserialization_scope! end def with_split_scope enter_split_scope! yield ensure exit_split_scope! end # Static methods to be used, the cases are defined by the usage from the above methods # if more methods are added - please extend the case statements as they are no longed dynamic def in_scope? name case name when :contrast in_contrast_scope? when :deserialization in_deserialization_scope? when :split in_split_scope? else raise NoMethodError, "Scope '#{ name.inspect }' is not registered as a scope." end end def enter_scope! name case name when :contrast enter_contrast_scope! when :deserialization enter_deserialization_scope! when :split enter_split_scope! else raise NoMethodError, "Scope '#{ name.inspect }' is not registered as a scope." end end def exit_scope! name case name when :contrast exit_contrast_scope! when :deserialization exit_deserialization_scope! when :split exit_split_scope! else raise NoMethodError, "Scope '#{ name.inspect }' is not registered as a scope." end end class << self def valid_scope? scope_sym Contrast::Agent::Scope::SCOPE_LIST.include? scope_sym end end end end end