# Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
# frozen_string_literal: true

module Contrast
  module Agent
    module Assess
      module Policy
        module Propagator
          # Propagation that results in all the tags of the source being
          # applied to the target. Unlike other propagators, this actually
          # results in new source nodes to track which columns in the database
          # have been tainted.
          class DatabaseWrite < Contrast::Agent::Assess::Policy::Propagator::Base
            class << self
              def propagate propagation_node, preshift, target
                return unless Contrast::ASSESS.require_dynamic_sources?

                class_type = preshift.object.cs__class
                class_name = class_type.cs__name
                tainted_columns = {}

                known_tainted = ::Contrast::ASSESS.tainted_columns[class_name]
                propagation_node.sources.each do |source|
                  handle_write(propagation_node, source, preshift, target, known_tainted, tainted_columns)
                end
                return if tainted_columns.empty?

                if known_tainted
                  known_tainted.concat(tainted_columns.keys)
                else
                  ::Contrast::ASSESS.tainted_columns[class_name] = tainted_columns.keys
                end

                Contrast::Agent::Assess::Policy::DynamicSourceFactory.create_sources class_type, tainted_columns
              end

              private

              def handle_write propagation_node, source, preshift, target, known_tainted, tainted_columns
                arg = preshift.args[source]
                return unless arg.cs__respond_to?(:each_pair)

                arg.each_pair do |key, value|
                  next unless value
                  next if known_tainted&.include?(key)
                  next unless (properties = Contrast::Agent::Assess::Tracker.properties!(value))

                  # TODO: RUBY-540 handle sanitization, handle nested objects
                  Contrast::Agent::Assess::Policy::PropagationMethod.apply_tags(propagation_node, value)
                  properties.build_event(propagation_node, value, preshift.object, target, preshift.args)
                  next unless tracked_value?(value)

                  tainted_columns[key] = properties
                end
              end
            end
          end
        end
      end
    end
  end
end