class RuboCop::Cop::Betterment::AuthorizationInController

Constants

MSG_UNSAFE_CREATE

MSG_UNSAFE_CREATE = 'Model created/updated using unsafe parameters'.freeze

Attributes

unsafe_parameters[RW]
unsafe_regex[RW]

Public Class Methods

new(config = nil, options = nil) click to toggle source
Calls superclass method
# File lib/rubocop/cop/betterment/authorization_in_controller.rb, line 34
def initialize(config = nil, options = nil)
  super(config, options)
  config = @config.for_cop(self)
  @unsafe_parameters = config.fetch("unsafe_parameters", []).map(&:to_sym)
  @unsafe_regex = Regexp.new config.fetch("unsafe_regex", ".*_id$")
  @wrapper_methods = {}
  @wrapper_names = []
end

Public Instance Methods

on_class(node) click to toggle source
# File lib/rubocop/cop/betterment/authorization_in_controller.rb, line 43
def on_class(node)
  track_methods(node)
  track_assignments(node)
end
on_send(node) click to toggle source
# File lib/rubocop/cop/betterment/authorization_in_controller.rb, line 48
def on_send(node) # rubocop:disable Metrics/AbcSize,Metrics/PerceivedComplexity
  _receiver_node, _method_name, *arg_nodes = *node

  return if !model_new?(node) && !model_update?(node)

  arg_nodes.each do |argument|
    if argument.send_type?
      tag_unsafe_param_hash(argument)
      tag_unsafe_param_permit_wrapper(argument)
    elsif argument.variable?
      tag_unsafe_param_permit_wrapper(argument)
    elsif argument.hash_type?
      argument.children.each do |pair|
        next if pair.type != :pair

        _key, value = *pair.children
        tag_unsafe_param_hash(value)
        tag_unsafe_param_permit_wrapper(value)
      end
    end
  end
end

Private Instance Methods

array_or_hash?(arg) click to toggle source
# File lib/rubocop/cop/betterment/authorization_in_controller.rb, line 185
def array_or_hash?(arg)
  %i(array hash).include?(arg.type)
end
contains_id_param?(arg_nodes) click to toggle source
# File lib/rubocop/cop/betterment/authorization_in_controller.rb, line 189
def contains_id_param?(arg_nodes)
  arg_nodes.any? do |arg|
    sym_or_str?(arg) && suspicious_id?(arg.value) ||
      array_or_hash?(arg) && contains_id_param?(arg.values)
  end
end
extract_parameter(argument) click to toggle source
# File lib/rubocop/cop/betterment/authorization_in_controller.rb, line 201
def extract_parameter(argument)
  _receiver_node, method_name, *arg_nodes = *argument
  return unless argument.send_type? && method_name == :[]

  argument_name = Utils::Parser.get_root_token(argument)

  if param_symbol?(argument_name) || @wrapper_names.include?(argument_name)
    arg_nodes.find do |arg|
      sym_or_str?(arg) && suspicious_id?(arg.value)
    end
  end
end
get_all_assignments(node) click to toggle source
# File lib/rubocop/cop/betterment/authorization_in_controller.rb, line 132
def get_all_assignments(node)
  return [] unless node.children && node.class_type?

  node.descendants.select do |descendant|
    _lhs, rhs = *descendant
    descendant.equals_asgn? && (descendant.type != :casgn) && rhs&.send_type?
  end
end
get_all_methods(node) click to toggle source
# File lib/rubocop/cop/betterment/authorization_in_controller.rb, line 145
def get_all_methods(node)
  return [] unless node.children && node.class_type?

  node.descendants.select do |descendant|
    descendant.def_type?
  end
end
get_param_wrappers(methods) click to toggle source

this finds all calls to any method on a params like object then walks up to find calls to permit if the arguments to permit are “suspicious”, then we add the whole method to a list of methods that wrap params.permit

# File lib/rubocop/cop/betterment/authorization_in_controller.rb, line 157
def get_param_wrappers(methods) # rubocop:disable Metrics/AbcSize,Metrics/PerceivedComplexity
  wrappers = []

  methods.each do |method|
    return [] unless method.def_type? || method.send_type?

    method.descendants.each do |child|
      next unless child.send_type?
      next unless param_symbol?(child.method_name)

      child.ancestors.each do |ancestor|
        _receiver_node, method_name, *arg_nodes = *ancestor
        next unless ancestor.send_type?
        next unless method_name == :permit

        # we're only interested in calls to params.....permit(...)
        wrappers << method if contains_id_param?(arg_nodes)
      end
    end
  end

  wrappers
end
param_symbol?(name) click to toggle source
# File lib/rubocop/cop/betterment/authorization_in_controller.rb, line 141
def param_symbol?(name)
  name == :params
end
suspicious_id?(symbol_name) click to toggle source

check a symbol name against the cop's config parameters

# File lib/rubocop/cop/betterment/authorization_in_controller.rb, line 197
def suspicious_id?(symbol_name)
  @unsafe_parameters.include?(symbol_name.to_sym) || @unsafe_regex.match(symbol_name) # symbol_name.to_s.end_with?("_id")
end
sym_or_str?(arg) click to toggle source
# File lib/rubocop/cop/betterment/authorization_in_controller.rb, line 181
def sym_or_str?(arg)
  %i(sym str).include?(arg.type)
end
tag_unsafe_param_hash(node) click to toggle source
# File lib/rubocop/cop/betterment/authorization_in_controller.rb, line 73
def tag_unsafe_param_hash(node)
  unsafe_param = extract_parameter(node)
  add_offense(unsafe_param, message: MSG_UNSAFE_CREATE) if unsafe_param
end
tag_unsafe_param_permit_wrapper(node) click to toggle source
# File lib/rubocop/cop/betterment/authorization_in_controller.rb, line 78
def tag_unsafe_param_permit_wrapper(node)
  return if !node.send_type? && !node.variable?
  return if node.send_type? && (node.method_name == :[])

  name = Utils::Parser.get_root_token(node)
  add_offense(node, message: MSG_UNSAFE_CREATE) if @wrapper_names.include?(name)
end
track_assignments(node) click to toggle source

keep track of all assignments that hold parameters

# File lib/rubocop/cop/betterment/authorization_in_controller.rb, line 103
def track_assignments(node)
  get_all_assignments(node).each do |assignment|
    variable_name, value = *assignment

    # if rhs calls params.permit, eg
    # - @var = params.permit(...)
    rhs_wrapper = get_param_wrappers([value])
    unless rhs_wrapper.empty?
      @wrapper_methods[variable_name] = rhs_wrapper[0]
      @wrapper_names << variable_name
    end

    # if rhs is a call to a parameter wrapper, eg
    # @var = parameter_wrapper
    root_token = Utils::Parser.get_root_token(value)
    if root_token && @wrapper_names.include?(root_token)
      @wrapper_methods[variable_name] = assignment
      @wrapper_names << variable_name
    end

    # if rhs extracts a parameter, eg
    # @var = params[:user_id]
    if extract_parameter(value)
      @wrapper_methods[variable_name] = assignment
      @wrapper_names << variable_name
    end
  end
end
track_methods(node) click to toggle source

if a method returns params or params.permit(…) then it will be tracked; if the method does not explicitly return a params sourced argument, then it will not be tracked

# File lib/rubocop/cop/betterment/authorization_in_controller.rb, line 89
def track_methods(node)
  methods = get_all_methods(node)

  methods.map do |method|
    method_returns = Utils::Parser.get_return_values(method)

    unless get_param_wrappers(method_returns).empty?
      @wrapper_methods[method.method_name] = method
      @wrapper_names << method.method_name
    end
  end
end