# 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 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 # 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