Prevent duplicate filters for encrypted attributes

When an Active Record encrypted attribute is declared, a filter for it
is automatically added to `config.filter_parameters`.  Prior to this
commit, the filter would be re-added every time the model was reloaded:

  ```ruby
  class Post < ActiveRecord::Base
    encrypts :title
  end
  ```

  ```irb
  irb> Rails.application.config.filter_parameters
  # => [:passw, ..., :ssn]

  irb> Post

  irb> Rails.application.config.filter_parameters
  # => [:passw, ..., :ssn, "post.title"]

  irb> reload!
  irb> Post

  irb> Rails.application.config.filter_parameters
  # => [:passw, ..., :ssn, "post.title", "post.title"]
  ```

This commit ensures filters are only added once so that
`config.filter_parameters` does not grow unbounded.
This commit is contained in:
Jonathan Hefner 2023-05-09 16:21:02 -05:00
parent 2b8b45a850
commit b3cecf09d6
2 changed files with 25 additions and 4 deletions

@ -48,11 +48,11 @@ def encrypted_attribute_was_declared(klass, name) # :nodoc:
end end
end end
def install_auto_filtered_parameters_hook(application) # :nodoc: def install_auto_filtered_parameters_hook(app) # :nodoc:
ActiveRecord::Encryption.on_encrypted_attribute_declared do |klass, encrypted_attribute_name| ActiveRecord::Encryption.on_encrypted_attribute_declared do |klass, encrypted_attribute_name|
filter_parameter = [("#{klass.model_name.element}" if klass.name), encrypted_attribute_name.to_s].compact.join(".") filter = [("#{klass.model_name.element}" if klass.name), encrypted_attribute_name.to_s].compact.join(".")
unless excluded_from_filter_parameters?(filter_parameter) unless excluded_from_filter_parameters?(filter)
application.config.filter_parameters << filter_parameter app.config.filter_parameters << filter unless app.config.filter_parameters.include?(filter)
klass.filter_attributes += [encrypted_attribute_name] klass.filter_attributes += [encrypted_attribute_name]
end end
end end

@ -376,6 +376,27 @@ def self.<(_)
assert_nil ActiveRecord::Scoping::ScopeRegistry.current_scope(Post) assert_nil ActiveRecord::Scoping::ScopeRegistry.current_scope(Post)
end end
test "filters for Active Record encrypted attributes are added to config.filter_parameters only once" do
rails %w(generate model post title:string)
rails %w(db:migrate)
app_file "app/models/post.rb", <<~RUBY
class Post < ActiveRecord::Base
encrypts :title
end
RUBY
require "#{app_path}/config/environment"
assert Post
filter_parameters = Rails.application.config.filter_parameters.dup
reload
assert Post
assert_equal filter_parameters, Rails.application.config.filter_parameters
end
test "ActiveRecord::MessagePack extensions are installed when using ActiveSupport::MessagePack::CacheSerializer" do test "ActiveRecord::MessagePack extensions are installed when using ActiveSupport::MessagePack::CacheSerializer" do
rails %w(generate model post title:string) rails %w(generate model post title:string)
rails %w(db:migrate) rails %w(db:migrate)