# frozen_string_literal: true

module RailsBestPractices
  module Reviews
    # Review model files to make sure to use attr_accessible, attr_protected or strong_parameters to protect mass assignment.
    #
    # See the best practices details here https://rails-bestpractices.com/posts/2012/03/06/protect-mass-assignment/
    #
    # Implmentation:
    #
    # Review process:
    #   check nodes to see if there is a command with message attr_accessible or attr_protected,
    #   or include ActiveModel::ForbiddenAttributesProtection.

    class ProtectMassAssignmentReview < Review
      interesting_files MODEL_FILES
      interesting_nodes :class, :command, :var_ref, :vcall, :fcall
      url 'https://rails-bestpractices.com/posts/2012/03/06/protect-mass-assignment/'

      # we treat it as mass assignment by default.
      add_callback :start_class do |_node|
        @mass_assignement = true
        check_activerecord_version
        check_whitelist_attributes_config
        check_include_forbidden_attributes_protection_config
      end

      # check if it is ActiveRecord::Base subclass and
      # if it sets config.active_record.whitelist_attributes to true.
      add_callback :end_class do |node|
        check_active_record(node)

        add_error 'protect mass assignment' if @mass_assignement
      end

      # check if it is attr_accessible or attr_protected command,
      # if it uses strong_parameters,
      # and if it uses devise.
      add_callback :start_command do |node|
        check_rails_builtin(node)
        check_strong_parameters(node)
        check_devise(node)
      end

      # check if it is attr_accessible, attr_protected, acts_as_authlogic without parameters.
      add_callback :start_var_ref, :start_vcall do |node|
        check_rails_builtin(node)
        check_authlogic(node)
      end

      # check if it uses authlogic.
      add_callback :start_fcall do |node|
        check_authlogic(node)
      end

      private

      def check_activerecord_version
        if Prepares.gems.gem_version('activerecord').to_i > 3
          @mass_assignement = false
        end
      end

      def check_whitelist_attributes_config
        if Prepares.configs['config.active_record.whitelist_attributes'] == 'true'
          @whitelist_attributes = true
        end
      end

      def check_include_forbidden_attributes_protection_config
        if Prepares.configs['railsbp.include_forbidden_attributes_protection'] == 'true'
          @mass_assignement = false
        end
      end

      def check_rails_builtin(node)
        if @whitelist_attributes ||
             [node.to_s, node.message.to_s].any? { |str| %w[attr_accessible attr_protected].include? str }
          @mass_assignement = false
        end
      end

      def check_strong_parameters(command_node)
        if command_node.message.to_s == 'include' &&
             command_node.arguments.all.first.to_s == 'ActiveModel::ForbiddenAttributesProtection'
          @mass_assignement = false
        end
      end

      def check_devise(command_node)
        if command_node.message.to_s == 'devise'
          @mass_assignement = false
        end
      end

      def check_authlogic(node)
        if [node.to_s, node.message.to_s].include? 'acts_as_authentic'
          @mass_assignement = false
        end
      end

      def check_active_record(const_path_ref_node)
        if const_path_ref_node.base_class.to_s != 'ActiveRecord::Base'
          @mass_assignement = false
        end
      end
    end
  end
end
