class ActiveSupport::ParameterFilter

Active Support Parameter Filter

ParameterFilter replaces values in a Hash-like object if their keys match one of the specified filters.

Matching based on nested keys is possible by using dot notation, e.g. "credit_card.number".

If a proc is given as a filter, each key and value of the Hash-like and of any nested Hashes will be passed to it. The value or key can then be mutated as desired using methods such as String#replace.

# Replaces values with "[FILTERED]" for keys that match /password/i.
ActiveSupport::ParameterFilter.new([:password])

# Replaces values with "[FILTERED]" for keys that match /foo|bar/i.
ActiveSupport::ParameterFilter.new([:foo, "bar"])

# Replaces values for the exact key "pin" and for keys that begin with
# "pin_". Does not match keys that otherwise include "pin" as a
# substring, such as "shipping_id".
ActiveSupport::ParameterFilter.new([/\Apin\z/, /\Apin_/])

# Replaces the value for :code in `{ credit_card: { code: "xxxx" } }`.
# Does not change `{ file: { code: "xxxx" } }`.
ActiveSupport::ParameterFilter.new(["credit_card.code"])

# Reverses values for keys that match /secret/i.
ActiveSupport::ParameterFilter.new([-> (k, v) do
  v.reverse! if /secret/i.match?(k)
end])

Public Class Methods

new(filters = [], mask: FILTERED) click to toggle source

Create instance with given filters. Supported type of filters are String, Regexp, and Proc. Other types of filters are treated as String using to_s. For Proc filters, key, value, and optional original hash is passed to block arguments.

Options

  • :mask - A replaced object when filtered. Defaults to "[FILTERED]".

# File lib/active_support/parameter_filter.rb, line 77
def initialize(filters = [], mask: FILTERED)
  @mask = mask
  compile_filters!(filters)
end
precompile_filters(filters) click to toggle source

Precompiles an array of filters that otherwise would be passed directly to initialize. Depending on the quantity and types of filters, precompilation can improve filtering performance, especially in the case where the ParameterFilter instance itself cannot be retained (but the precompiled filters can be retained).

filters = [/foo/, :bar, "nested.baz", /nested\.qux/]

precompiled = ActiveSupport::ParameterFilter.precompile_filters(filters)
# => [/(?-mix:foo)|(?i:bar)/, /(?i:nested\.baz)|(?-mix:nested\.qux)/]

ActiveSupport::ParameterFilter.new(precompiled)
# File lib/active_support/parameter_filter.rb, line 55
def self.precompile_filters(filters)
  filters, patterns = filters.partition { |filter| filter.is_a?(Proc) }

  patterns.map! do |pattern|
    pattern.is_a?(Regexp) ? pattern : "(?i:#{Regexp.escape pattern.to_s})"
  end

  deep_patterns = patterns.extract! { |pattern| pattern.to_s.include?("\\.") }

  filters << Regexp.new(patterns.join("|")) if patterns.any?
  filters << Regexp.new(deep_patterns.join("|")) if deep_patterns.any?

  filters
end

Public Instance Methods

filter(params) click to toggle source

Mask value of params if key matches one of filters.

# File lib/active_support/parameter_filter.rb, line 83
def filter(params)
  @no_filters ? params.dup : call(params)
end
filter_param(key, value) click to toggle source

Returns filtered value for given key. For Proc filters, third block argument is not populated.

# File lib/active_support/parameter_filter.rb, line 88
def filter_param(key, value)
  @no_filters ? value : value_for_key(key, value)
end

Private Instance Methods

call(params, full_parent_key = nil, original_params = params) click to toggle source
# File lib/active_support/parameter_filter.rb, line 125
def call(params, full_parent_key = nil, original_params = params)
  filtered_params = params.class.new

  params.each do |key, value|
    filtered_params[key] = value_for_key(key, value, full_parent_key, original_params)
  end

  filtered_params
end
compile_filters!(filters) click to toggle source
# File lib/active_support/parameter_filter.rb, line 93
def compile_filters!(filters)
  @no_filters = filters.empty?
  return if @no_filters

  @regexps, strings = [], []
  @deep_regexps, deep_strings = nil, nil
  @blocks = nil

  filters.each do |item|
    case item
    when Proc
      (@blocks ||= []) << item
    when Regexp
      if item.to_s.include?("\\.")
        (@deep_regexps ||= []) << item
      else
        @regexps << item
      end
    else
      s = Regexp.escape(item.to_s)
      if s.include?("\\.")
        (deep_strings ||= []) << s
      else
        strings << s
      end
    end
  end

  @regexps << Regexp.new(strings.join("|"), true) unless strings.empty?
  (@deep_regexps ||= []) << Regexp.new(deep_strings.join("|"), true) if deep_strings
end
value_for_key(key, value, full_parent_key = nil, original_params = nil) click to toggle source
# File lib/active_support/parameter_filter.rb, line 135
def value_for_key(key, value, full_parent_key = nil, original_params = nil)
  if @deep_regexps
    full_key = full_parent_key ? "#{full_parent_key}.#{key}" : key.to_s
  end

  if @regexps.any? { |r| r.match?(key.to_s) }
    value = @mask
  elsif @deep_regexps&.any? { |r| r.match?(full_key) }
    value = @mask
  elsif value.is_a?(Hash)
    value = call(value, full_key, original_params)
  elsif value.is_a?(Array)
    value = value.map { |v| value_for_key(key, v, full_parent_key, original_params) }
  elsif @blocks
    key = key.dup if key.duplicable?
    value = value.dup if value.duplicable?
    @blocks.each { |b| b.arity == 2 ? b.call(key, value) : b.call(key, value, original_params) }
  end

  value
end