diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f3206a38..63654f5c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,24 +15,12 @@ jobs: gemfile: gemfiles/rails71.gemfile - ruby: 3.1 gemfile: gemfiles/rails70.gemfile - - ruby: "3.0" - gemfile: gemfiles/rails61.gemfile - - ruby: 2.7 - gemfile: gemfiles/rails60.gemfile - - ruby: 2.6 - gemfile: gemfiles/rails52.gemfile - ruby: 3.3 gemfile: gemfiles/mongoid9.gemfile mongodb: true - ruby: 3.1 gemfile: gemfiles/mongoid8.gemfile mongodb: true - - ruby: 2.7 - gemfile: gemfiles/mongoid7.gemfile - mongodb: true - - ruby: 2.6 - gemfile: gemfiles/mongoid6.gemfile - mongodb: true env: BUNDLE_GEMFILE: ${{ matrix.gemfile }} steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a2626fa..d5687479 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.0.0 (unreleased) + +- Dropped support for Active Record < 7 and Ruby < 3.1 +- Dropped support for Mongoid < 8 + ## 1.4.1 (2024-09-09) - Fixed error message for previews for Active Storage 7.1.4 diff --git a/gemfiles/mongoid6.gemfile b/gemfiles/mongoid6.gemfile deleted file mode 100644 index c6a9f682..00000000 --- a/gemfiles/mongoid6.gemfile +++ /dev/null @@ -1,14 +0,0 @@ -source "https://rubygems.org" - -gemspec path: ".." - -gem "rake" -gem "minitest", ">= 5" - -gem "mongoid", "~> 6" -gem "rails" -gem "carrierwave" -gem "combustion", ">= 1.3" -gem "rbnacl", ">= 6" -gem "shrine" -gem "shrine-mongoid" diff --git a/gemfiles/mongoid7.gemfile b/gemfiles/mongoid7.gemfile deleted file mode 100644 index 6ed87577..00000000 --- a/gemfiles/mongoid7.gemfile +++ /dev/null @@ -1,14 +0,0 @@ -source "https://rubygems.org" - -gemspec path: ".." - -gem "rake" -gem "minitest", ">= 5" - -gem "mongoid", "~> 7" -gem "rails" -gem "carrierwave" -gem "combustion", ">= 1.3" -gem "rbnacl", ">= 6" -gem "shrine" -gem "shrine-mongoid" diff --git a/gemfiles/rails52.gemfile b/gemfiles/rails52.gemfile deleted file mode 100644 index eb5be93f..00000000 --- a/gemfiles/rails52.gemfile +++ /dev/null @@ -1,15 +0,0 @@ -source "https://rubygems.org" - -gemspec path: ".." - -gem "rake" -gem "minitest", ">= 5" - -gem "rails", "~> 5.2.0" -gem "carrierwave", "~> 2" -gem "combustion", ">= 1.3" -gem "rbnacl", ">= 6" -gem "sqlite3", "< 2" -gem "pg" -gem "mysql2" -gem "shrine" diff --git a/gemfiles/rails60.gemfile b/gemfiles/rails60.gemfile deleted file mode 100644 index e3a665bb..00000000 --- a/gemfiles/rails60.gemfile +++ /dev/null @@ -1,15 +0,0 @@ -source "https://rubygems.org" - -gemspec path: ".." - -gem "rake" -gem "minitest", ">= 5" - -gem "rails", "~> 6.0.0" -gem "carrierwave", "~> 2" -gem "combustion", ">= 1.3" -gem "rbnacl", ">= 6" -gem "sqlite3", "< 2" -gem "pg" -gem "mysql2" -gem "shrine" diff --git a/gemfiles/rails61.gemfile b/gemfiles/rails61.gemfile deleted file mode 100644 index e190fbef..00000000 --- a/gemfiles/rails61.gemfile +++ /dev/null @@ -1,15 +0,0 @@ -source "https://rubygems.org" - -gemspec path: ".." - -gem "rake" -gem "minitest", ">= 5" - -gem "rails", "~> 6.1.0" -gem "carrierwave", "~> 2" -gem "combustion", ">= 1.3" -gem "rbnacl", ">= 6" -gem "sqlite3", "< 2" -gem "pg" -gem "mysql2" -gem "shrine" diff --git a/lib/generators/lockbox/audits_generator.rb b/lib/generators/lockbox/audits_generator.rb index baf044af..6676d041 100644 --- a/lib/generators/lockbox/audits_generator.rb +++ b/lib/generators/lockbox/audits_generator.rb @@ -29,11 +29,7 @@ def data_type # use connection_config instead of connection.adapter # so database connection isn't needed def adapter - if ActiveRecord::VERSION::STRING.to_f >= 6.1 - ActiveRecord::Base.connection_db_config.adapter.to_s - else - ActiveRecord::Base.connection_config[:adapter].to_s - end + ActiveRecord::Base.connection_db_config.adapter.to_s end end end diff --git a/lib/lockbox.rb b/lib/lockbox.rb index bd18e19a..c8b15ed5 100644 --- a/lib/lockbox.rb +++ b/lib/lockbox.rb @@ -99,8 +99,10 @@ def self.encrypts_action_text_body(**options) if defined?(ActiveSupport.on_load) ActiveSupport.on_load(:active_record) do ar_version = ActiveRecord::VERSION::STRING.to_f - if ar_version < 5.2 - if ar_version >= 5 + if ar_version < 7 + if ar_version >= 5.2 + raise Lockbox::Error, "Active Record #{ActiveRecord::VERSION::STRING} requires Lockbox < 2" + elsif ar_version >= 5 raise Lockbox::Error, "Active Record #{ActiveRecord::VERSION::STRING} requires Lockbox < 0.7" else raise Lockbox::Error, "Active Record #{ActiveRecord::VERSION::STRING} not supported" @@ -109,11 +111,19 @@ def self.encrypts_action_text_body(**options) extend Lockbox::Model extend Lockbox::Model::Attached - singleton_class.alias_method(:encrypts, :lockbox_encrypts) if ActiveRecord::VERSION::MAJOR < 7 ActiveRecord::Relation.prepend Lockbox::Calculations end ActiveSupport.on_load(:mongoid) do + mongoid_version = Mongoid::VERSION.to_i + if mongoid_version < 8 + if mongoid_version >= 6 + raise Lockbox::Error, "Mongoid #{Mongoid::VERSION} requires Lockbox < 2" + else + raise Lockbox::Error, "Mongoid #{Mongoid::VERSION} not supported" + end + end + Mongoid::Document::ClassMethods.include(Lockbox::Model) Mongoid::Document::ClassMethods.alias_method(:encrypts, :lockbox_encrypts) end diff --git a/lib/lockbox/active_storage_extensions.rb b/lib/lockbox/active_storage_extensions.rb index cbc3923b..dda13324 100644 --- a/lib/lockbox/active_storage_extensions.rb +++ b/lib/lockbox/active_storage_extensions.rb @@ -34,13 +34,6 @@ def encrypt_attachable(attachable) end module AttachedOne - if ActiveStorage::VERSION::MAJOR < 6 - def attach(attachable) - attachable = encrypt_attachable(attachable) if encrypted? - super(attachable) - end - end - def rotate_encryption! raise "Not encrypted" unless encrypted? @@ -51,19 +44,6 @@ def rotate_encryption! end module AttachedMany - if ActiveStorage::VERSION::MAJOR < 6 - def attach(*attachables) - if encrypted? - attachables = - attachables.flatten.collect do |attachable| - encrypt_attachable(attachable) - end - end - - super(attachables) - end - end - def rotate_encryption! raise "Not encrypted" unless encrypted? @@ -131,27 +111,25 @@ def transform_variants_later end end - if ActiveStorage::VERSION::MAJOR >= 6 - def open(**options) - blob.open(**options) do |file| - options = Utils.encrypted_options(record, name) - # only trust the metadata when migrating - # as earlier versions of Lockbox won't have it - # and it's not a good practice to trust modifiable data - encrypted = options && (!options[:migrating] || blob.metadata["encrypted"]) - if encrypted - result = Utils.decrypt_result(record, name, options, file.read) - file.rewind - # truncate may not be available on all platforms - # according to the Ruby docs - # may need to create a new temp file instead - file.truncate(0) - file.write(result) - file.rewind - end - - yield file + def open(**options) + blob.open(**options) do |file| + options = Utils.encrypted_options(record, name) + # only trust the metadata when migrating + # as earlier versions of Lockbox won't have it + # and it's not a good practice to trust modifiable data + encrypted = options && (!options[:migrating] || blob.metadata["encrypted"]) + if encrypted + result = Utils.decrypt_result(record, name, options, file.read) + file.rewind + # truncate may not be available on all platforms + # according to the Ruby docs + # may need to create a new temp file instead + file.truncate(0) + file.write(result) + file.rewind end + + yield file end end end diff --git a/lib/lockbox/model.rb b/lib/lockbox/model.rb index d04c9671..9ea0e715 100644 --- a/lib/lockbox/model.rb +++ b/lib/lockbox/model.rb @@ -60,7 +60,7 @@ def has_encrypted(*attributes, **options) class_eval do # Lockbox uses custom inspect # but this could be useful for other gems - if activerecord && ActiveRecord::VERSION::MAJOR >= 6 + if activerecord # only add virtual attribute # need to use regexp since strings do partial matching # also, need to use += instead of << @@ -232,71 +232,69 @@ def update_columns(attributes) result end - if ActiveRecord::VERSION::MAJOR >= 6 - if ActiveRecord::VERSION::STRING.to_f >= 7.2 - def self.insert(attributes, **options) - super(lockbox_map_record_attributes(attributes), **options) - end - - def self.insert!(attributes, **options) - super(lockbox_map_record_attributes(attributes), **options) - end - - def self.upsert(attributes, **options) - super(lockbox_map_record_attributes(attributes, check_readonly: true), **options) - end + if ActiveRecord::VERSION::STRING.to_f >= 7.2 + def self.insert(attributes, **options) + super(lockbox_map_record_attributes(attributes), **options) end - def self.insert_all(attributes, **options) - super(lockbox_map_attributes(attributes), **options) + def self.insert!(attributes, **options) + super(lockbox_map_record_attributes(attributes), **options) end - def self.insert_all!(attributes, **options) - super(lockbox_map_attributes(attributes), **options) + def self.upsert(attributes, **options) + super(lockbox_map_record_attributes(attributes, check_readonly: true), **options) end + end - def self.upsert_all(attributes, **options) - super(lockbox_map_attributes(attributes, check_readonly: true), **options) - end + def self.insert_all(attributes, **options) + super(lockbox_map_attributes(attributes), **options) + end - # private - # does not try to handle :returning option for simplicity - def self.lockbox_map_attributes(records, check_readonly: false) - return records unless records.is_a?(Array) + def self.insert_all!(attributes, **options) + super(lockbox_map_attributes(attributes), **options) + end - records.map do |attributes| - lockbox_map_record_attributes(attributes, check_readonly: false) - end - end + def self.upsert_all(attributes, **options) + super(lockbox_map_attributes(attributes, check_readonly: true), **options) + end - # private - def self.lockbox_map_record_attributes(attributes, check_readonly: false) - return attributes unless attributes.is_a?(Hash) + # private + # does not try to handle :returning option for simplicity + def self.lockbox_map_attributes(records, check_readonly: false) + return records unless records.is_a?(Array) - # transform keys like Active Record - attributes = attributes.transform_keys do |key| - n = key.to_s - attribute_aliases[n] || n - end + records.map do |attributes| + lockbox_map_record_attributes(attributes, check_readonly: false) + end + end - lockbox_attributes = self.lockbox_attributes.slice(*attributes.keys.map(&:to_sym)) - lockbox_attributes.each do |key, lockbox_attribute| - attribute = key.to_s - # check read only - # users should mark both plaintext and ciphertext columns - if check_readonly && readonly_attributes.include?(attribute) && !readonly_attributes.include?(lockbox_attribute[:encrypted_attribute].to_s) - warn "[lockbox] WARNING: Mark attribute as readonly: #{lockbox_attribute[:encrypted_attribute]}" - end + # private + def self.lockbox_map_record_attributes(attributes, check_readonly: false) + return attributes unless attributes.is_a?(Hash) - message = attributes[attribute] - attributes.delete(attribute) unless lockbox_attribute[:migrating] - encrypted_attribute = lockbox_attribute[:encrypted_attribute] - ciphertext = send("generate_#{encrypted_attribute}", message) - attributes[encrypted_attribute] = ciphertext + # transform keys like Active Record + attributes = attributes.transform_keys do |key| + n = key.to_s + attribute_aliases[n] || n + end + + lockbox_attributes = self.lockbox_attributes.slice(*attributes.keys.map(&:to_sym)) + lockbox_attributes.each do |key, lockbox_attribute| + attribute = key.to_s + # check read only + # users should mark both plaintext and ciphertext columns + if check_readonly && readonly_attributes.include?(attribute) && !readonly_attributes.include?(lockbox_attribute[:encrypted_attribute].to_s) + warn "[lockbox] WARNING: Mark attribute as readonly: #{lockbox_attribute[:encrypted_attribute]}" end - attributes + message = attributes[attribute] + attributes.delete(attribute) unless lockbox_attribute[:migrating] + encrypted_attribute = lockbox_attribute[:encrypted_attribute] + ciphertext = send("generate_#{encrypted_attribute}", message) + attributes[encrypted_attribute] = ciphertext end + + attributes end else def reload @@ -327,13 +325,8 @@ def reload elsif attributes_to_define_after_schema_loads.key?(name.to_s) opt = attributes_to_define_after_schema_loads[name.to_s][1] - has_default = - if ActiveRecord::VERSION::MAJOR >= 7 - # not ideal, since NO_DEFAULT_PROVIDED is private - opt != ActiveRecord::Attributes::ClassMethods.const_get(:NO_DEFAULT_PROVIDED) - else - opt.is_a?(Hash) && opt.key?(:default) - end + # not ideal, since NO_DEFAULT_PROVIDED is private + has_default = opt != ActiveRecord::Attributes::ClassMethods.const_get(:NO_DEFAULT_PROVIDED) if has_default warn "[lockbox] WARNING: attributes with `:default` option are not supported. Use `after_initialize` instead." @@ -413,16 +406,11 @@ def reload # otherwise, type gets set to ActiveModel::Type::Value # which always returns false for changed_in_place? # earlier versions of Active Record take the previous code path - if ActiveRecord::VERSION::STRING.to_f >= 7.0 && attributes_to_define_after_schema_loads[name.to_s].first.is_a?(Proc) + if attributes_to_define_after_schema_loads[name.to_s].first.is_a?(Proc) attribute_type = attributes_to_define_after_schema_loads[name.to_s].first.call(nil) if attribute_type.is_a?(ActiveRecord::Type::Serialized) && attribute_type.subtype.nil? attribute name, ActiveRecord::Type::Serialized.new(ActiveRecord::Type::String.new, attribute_type.coder) end - elsif ActiveRecord::VERSION::STRING.to_f >= 6.1 && attributes_to_define_after_schema_loads[name.to_s].first.is_a?(Proc) - attribute_type = attributes_to_define_after_schema_loads[name.to_s].first.call - if attribute_type.is_a?(ActiveRecord::Type::Serialized) && attribute_type.subtype.nil? - attribute name, ActiveRecord::Type::Serialized.new(ActiveRecord::Type::String.new, attribute_type.coder) - end end end @@ -606,14 +594,7 @@ def reload # double precision, big endian message = [message].pack("G") unless message.nil? when :decimal - message = - if ActiveRecord::VERSION::MAJOR >= 6 - ActiveRecord::Type::Decimal.new.serialize(message) - else - # issue with serialize in Active Record < 6 - # https://github.com/rails/rails/commit/a741208f80dd33420a56486bd9ed2b0b9862234a - ActiveRecord::Type::Decimal.new.cast(message) - end + message = ActiveRecord::Type::Decimal.new.serialize(message) # Postgres stores 4 decimal digits in 2 bytes # plus 3 to 8 bytes of overhead # but use string for simplicity diff --git a/lib/lockbox/railtie.rb b/lib/lockbox/railtie.rb index f0f3abe6..825dbd57 100644 --- a/lib/lockbox/railtie.rb +++ b/lib/lockbox/railtie.rb @@ -12,32 +12,15 @@ class Railtie < Rails::Railtie require "lockbox/active_storage_extensions" ActiveStorage::Attached.prepend(Lockbox::ActiveStorageExtensions::Attached) - if ActiveStorage::VERSION::MAJOR >= 6 - ActiveStorage::Attached::Changes::CreateOne.prepend(Lockbox::ActiveStorageExtensions::CreateOne) - end + ActiveStorage::Attached::Changes::CreateOne.prepend(Lockbox::ActiveStorageExtensions::CreateOne) ActiveStorage::Attached::One.prepend(Lockbox::ActiveStorageExtensions::AttachedOne) ActiveStorage::Attached::Many.prepend(Lockbox::ActiveStorageExtensions::AttachedMany) - # use load hooks when possible - if ActiveStorage::VERSION::MAJOR >= 7 - ActiveSupport.on_load(:active_storage_attachment) do - prepend Lockbox::ActiveStorageExtensions::Attachment - end - ActiveSupport.on_load(:active_storage_blob) do - prepend Lockbox::ActiveStorageExtensions::Blob - end - elsif ActiveStorage::VERSION::MAJOR >= 6 - ActiveSupport.on_load(:active_storage_attachment) do - include Lockbox::ActiveStorageExtensions::Attachment - end - ActiveSupport.on_load(:active_storage_blob) do - prepend Lockbox::ActiveStorageExtensions::Blob - end - else - app.config.to_prepare do - ActiveStorage::Attachment.include(Lockbox::ActiveStorageExtensions::Attachment) - ActiveStorage::Blob.prepend(Lockbox::ActiveStorageExtensions::Blob) - end + ActiveSupport.on_load(:active_storage_attachment) do + prepend Lockbox::ActiveStorageExtensions::Attachment + end + ActiveSupport.on_load(:active_storage_blob) do + prepend Lockbox::ActiveStorageExtensions::Blob end end end diff --git a/lockbox.gemspec b/lockbox.gemspec index 81d75534..d4e04930 100644 --- a/lockbox.gemspec +++ b/lockbox.gemspec @@ -13,5 +13,5 @@ Gem::Specification.new do |spec| spec.files = Dir["*.{md,txt}", "{lib}/**/*"] spec.require_path = "lib" - spec.required_ruby_version = ">= 2.6" + spec.required_ruby_version = ">= 3.1" end diff --git a/test/action_text_test.rb b/test/action_text_test.rb index ab4e9c35..60b68b12 100644 --- a/test/action_text_test.rb +++ b/test/action_text_test.rb @@ -21,15 +21,10 @@ def test_encrypted def test_encoding user = User.create!(content: "ルビー") assert_equal "