lockbox 1.3.3 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2a238a9d70f5c46cabf8824957f739beb662730a2039696da29b6bc2f7a23462
4
- data.tar.gz: eaebb7f1bd209792eee41166c6b5878b99ea0ee9436d030bf76ff13c8c56e4e2
3
+ metadata.gz: b4771553bf23214b514e9a4a5c94ce665d234d34969f9a3941ea68bdc6729ed4
4
+ data.tar.gz: a921894d4f3f43d5245a91941e06f79ea652f09c9c77ccf8dc7e442d1dbda0e5
5
5
  SHA512:
6
- metadata.gz: cc5a1953cbc1493d5eba15ef0d0aed760ee6bd5d0f50e9810d522d57e46e2426f1a8573284834b262208fb6b20638ce15f0f52b5eb1c74dd9f5cc79c9124d6d1
7
- data.tar.gz: ab01a6601a0317e0182f49bff4410ab232f88a88a5e65b68d20c6b761a38c3777a527d66c6c1fc0896ce23fdeacb493ec5487a494bf310760581f0ab511c3be3
6
+ metadata.gz: 90fef82d743a0172a61728fd9f314eca597d43820e87aeb9cb77330e4ec824919d354b81cf4c6c9c2b1c8cedcebb8916425fed5e469b24ea11b6a2e8730feb6f
7
+ data.tar.gz: fe874ec6aa3f563927f2ea1743d34a795468846ce74cc9e1a0467e41757721d40c8449d7093b158f63f92b8566ea578cf88e5f2b0a8caefc9facee2a8b731afa
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## 1.4.1 (2024-09-09)
2
+
3
+ - Fixed error message for previews for Active Storage 7.1.4
4
+
5
+ ## 1.4.0 (2024-08-09)
6
+
7
+ - Added support for Active Record 7.2
8
+ - Added support for Mongoid 9
9
+ - Fixed error when `decryption_key` option is a proc or symbol and returns `nil`
10
+
1
11
  ## 1.3.3 (2024-02-07)
2
12
 
3
13
  - Added warning for encrypting store attributes
data/README.md CHANGED
@@ -72,7 +72,7 @@ Then follow the instructions below for the data you want to encrypt.
72
72
  Create a migration with:
73
73
 
74
74
  ```ruby
75
- class AddEmailCiphertextToUsers < ActiveRecord::Migration[7.1]
75
+ class AddEmailCiphertextToUsers < ActiveRecord::Migration[7.2]
76
76
  def change
77
77
  add_column :users, :email_ciphertext, :text
78
78
  end
@@ -194,7 +194,7 @@ class User < ApplicationRecord
194
194
  has_encrypted :email
195
195
 
196
196
  # remove this line after dropping email column
197
- self.ignored_columns = ["email"]
197
+ self.ignored_columns += ["email"]
198
198
  end
199
199
  ```
200
200
 
@@ -251,7 +251,7 @@ User.decrypt_email_ciphertext(user.email_ciphertext)
251
251
  Create a migration with:
252
252
 
253
253
  ```ruby
254
- class AddBodyCiphertextToRichTexts < ActiveRecord::Migration[7.1]
254
+ class AddBodyCiphertextToRichTexts < ActiveRecord::Migration[7.2]
255
255
  def change
256
256
  add_column :action_text_rich_texts, :body_ciphertext, :text
257
257
  end
@@ -382,7 +382,7 @@ Encryption is applied to all versions after processing.
382
382
  You can mount the uploader [as normal](https://github.com/carrierwaveuploader/carrierwave#activerecord). With Active Record, this involves creating a migration:
383
383
 
384
384
  ```ruby
385
- class AddLicenseToUsers < ActiveRecord::Migration[7.1]
385
+ class AddLicenseToUsers < ActiveRecord::Migration[7.2]
386
386
  def change
387
387
  add_column :users, :license, :string
388
388
  end
@@ -910,7 +910,7 @@ end
910
910
  You can use `binary` columns for the ciphertext instead of `text` columns.
911
911
 
912
912
  ```ruby
913
- class AddEmailCiphertextToUsers < ActiveRecord::Migration[7.1]
913
+ class AddEmailCiphertextToUsers < ActiveRecord::Migration[7.2]
914
914
  def change
915
915
  add_column :users, :email_ciphertext, :binary
916
916
  end
@@ -961,7 +961,7 @@ end
961
961
  Create a migration with:
962
962
 
963
963
  ```ruby
964
- class MigrateToLockbox < ActiveRecord::Migration[7.1]
964
+ class MigrateToLockbox < ActiveRecord::Migration[7.2]
965
965
  def change
966
966
  add_column :users, :name_ciphertext, :text
967
967
  add_column :users, :email_ciphertext, :text
@@ -994,7 +994,7 @@ end
994
994
  Then remove the previous gem from your Gemfile and drop its columns.
995
995
 
996
996
  ```ruby
997
- class RemovePreviousEncryptedColumns < ActiveRecord::Migration[7.1]
997
+ class RemovePreviousEncryptedColumns < ActiveRecord::Migration[7.2]
998
998
  def change
999
999
  remove_column :users, :encrypted_name, :text
1000
1000
  remove_column :users, :encrypted_name_iv, :text
@@ -1016,21 +1016,6 @@ class User < ApplicationRecord
1016
1016
  end
1017
1017
  ```
1018
1018
 
1019
- ### 0.6.0
1020
-
1021
- 0.6.0 adds `encrypted: true` to Active Storage metadata for new files. This field is informational, but if you prefer to add it to existing files, use:
1022
-
1023
- ```ruby
1024
- User.with_attached_license.find_each do |user|
1025
- next unless user.license.attached?
1026
-
1027
- metadata = user.license.metadata
1028
- unless metadata["encrypted"]
1029
- user.license.blob.update!(metadata: metadata.merge("encrypted" => true))
1030
- end
1031
- end
1032
- ```
1033
-
1034
1019
  ## History
1035
1020
 
1036
1021
  View the [changelog](https://github.com/ankane/lockbox/blob/master/CHANGELOG.md)
@@ -124,6 +124,13 @@ module Lockbox
124
124
  super
125
125
  end
126
126
 
127
+ if ActiveStorage::VERSION::STRING.to_f == 7.1 && ActiveStorage.version >= "7.1.4"
128
+ def transform_variants_later
129
+ blob.instance_variable_set(:@lockbox_encrypted, true) if Utils.encrypted_options(record, name)
130
+ super
131
+ end
132
+ end
133
+
127
134
  if ActiveStorage::VERSION::MAJOR >= 6
128
135
  def open(**options)
129
136
  blob.open(**options) do |file|
@@ -150,6 +157,12 @@ module Lockbox
150
157
  end
151
158
 
152
159
  module Blob
160
+ if ActiveStorage::VERSION::STRING.to_f == 7.1 && ActiveStorage.version >= "7.1.4"
161
+ def preview_image_needed_before_processing_variants?
162
+ !instance_variable_defined?(:@lockbox_encrypted) && super
163
+ end
164
+ end
165
+
153
166
  private
154
167
 
155
168
  def extract_content_type(io)
@@ -79,7 +79,7 @@ module Lockbox
79
79
  while uploader.parent_version
80
80
  uploader = uploader.parent_version
81
81
  end
82
- uploader.class.name.sub(/Uploader\z/, "").underscore
82
+ uploader.class.name.delete_suffix("Uploader").underscore
83
83
  end
84
84
  end
85
85
 
@@ -2,7 +2,7 @@ module Lockbox
2
2
  class Migrator
3
3
  def initialize(relation, batch_size:)
4
4
  @relation = relation
5
- @transaction = @relation.respond_to?(:transaction)
5
+ @transaction = @relation.respond_to?(:transaction) && !mongoid_relation?(base_relation)
6
6
  @batch_size = batch_size
7
7
  end
8
8
 
data/lib/lockbox/model.rb CHANGED
@@ -137,13 +137,16 @@ module Lockbox
137
137
  # essentially a no-op if already loaded
138
138
  # an exception is thrown if decryption fails
139
139
  self.class.lockbox_attributes.each do |_, lockbox_attribute|
140
- # don't try to decrypt if no decryption key given
141
- next if lockbox_attribute[:algorithm] == "hybrid" && lockbox_attribute[:decryption_key].nil?
142
-
143
140
  # it is possible that the encrypted attribute is not loaded, eg.
144
141
  # if the record was fetched partially (`User.select(:id).first`).
145
142
  # accessing a not loaded attribute raises an `ActiveModel::MissingAttributeError`.
146
- send(lockbox_attribute[:attribute]) if has_attribute?(lockbox_attribute[:encrypted_attribute])
143
+ if has_attribute?(lockbox_attribute[:encrypted_attribute])
144
+ begin
145
+ send(lockbox_attribute[:attribute])
146
+ rescue ArgumentError => e
147
+ raise e if e.message != "No decryption key set"
148
+ end
149
+ end
147
150
  end
148
151
  super
149
152
  end
@@ -230,6 +233,20 @@ module Lockbox
230
233
  end
231
234
 
232
235
  if ActiveRecord::VERSION::MAJOR >= 6
236
+ if ActiveRecord::VERSION::STRING.to_f >= 7.2
237
+ def self.insert(attributes, **options)
238
+ super(lockbox_map_record_attributes(attributes), **options)
239
+ end
240
+
241
+ def self.insert!(attributes, **options)
242
+ super(lockbox_map_record_attributes(attributes), **options)
243
+ end
244
+
245
+ def self.upsert(attributes, **options)
246
+ super(lockbox_map_record_attributes(attributes, check_readonly: true), **options)
247
+ end
248
+ end
249
+
233
250
  def self.insert_all(attributes, **options)
234
251
  super(lockbox_map_attributes(attributes), **options)
235
252
  end
@@ -248,30 +265,37 @@ module Lockbox
248
265
  return records unless records.is_a?(Array)
249
266
 
250
267
  records.map do |attributes|
251
- # transform keys like Active Record
252
- attributes = attributes.transform_keys do |key|
253
- n = key.to_s
254
- attribute_aliases[n] || n
255
- end
268
+ lockbox_map_record_attributes(attributes, check_readonly: false)
269
+ end
270
+ end
256
271
 
257
- lockbox_attributes = self.lockbox_attributes.slice(*attributes.keys.map(&:to_sym))
258
- lockbox_attributes.each do |key, lockbox_attribute|
259
- attribute = key.to_s
260
- # check read only
261
- # users should mark both plaintext and ciphertext columns
262
- if check_readonly && readonly_attributes.include?(attribute) && !readonly_attributes.include?(lockbox_attribute[:encrypted_attribute].to_s)
263
- warn "[lockbox] WARNING: Mark attribute as readonly: #{lockbox_attribute[:encrypted_attribute]}"
264
- end
265
-
266
- message = attributes[attribute]
267
- attributes.delete(attribute) unless lockbox_attribute[:migrating]
268
- encrypted_attribute = lockbox_attribute[:encrypted_attribute]
269
- ciphertext = send("generate_#{encrypted_attribute}", message)
270
- attributes[encrypted_attribute] = ciphertext
272
+ # private
273
+ def self.lockbox_map_record_attributes(attributes, check_readonly: false)
274
+ return attributes unless attributes.is_a?(Hash)
275
+
276
+ # transform keys like Active Record
277
+ attributes = attributes.transform_keys do |key|
278
+ n = key.to_s
279
+ attribute_aliases[n] || n
280
+ end
281
+
282
+ lockbox_attributes = self.lockbox_attributes.slice(*attributes.keys.map(&:to_sym))
283
+ lockbox_attributes.each do |key, lockbox_attribute|
284
+ attribute = key.to_s
285
+ # check read only
286
+ # users should mark both plaintext and ciphertext columns
287
+ if check_readonly && readonly_attributes.include?(attribute) && !readonly_attributes.include?(lockbox_attribute[:encrypted_attribute].to_s)
288
+ warn "[lockbox] WARNING: Mark attribute as readonly: #{lockbox_attribute[:encrypted_attribute]}"
271
289
  end
272
290
 
273
- attributes
291
+ message = attributes[attribute]
292
+ attributes.delete(attribute) unless lockbox_attribute[:migrating]
293
+ encrypted_attribute = lockbox_attribute[:encrypted_attribute]
294
+ ciphertext = send("generate_#{encrypted_attribute}", message)
295
+ attributes[encrypted_attribute] = ciphertext
274
296
  end
297
+
298
+ attributes
275
299
  end
276
300
  end
277
301
  else
@@ -295,7 +319,12 @@ module Lockbox
295
319
  end
296
320
 
297
321
  # warn on default attributes
298
- if attributes_to_define_after_schema_loads.key?(name.to_s)
322
+ if ActiveRecord::VERSION::STRING.to_f >= 7.2
323
+ # TODO improve
324
+ if pending_attribute_modifications.any? { |v| v.is_a?(ActiveModel::AttributeRegistration::ClassMethods::PendingDefault) && v.name == name.to_s }
325
+ warn "[lockbox] WARNING: attributes with `:default` option are not supported. Use `after_initialize` instead."
326
+ end
327
+ elsif attributes_to_define_after_schema_loads.key?(name.to_s)
299
328
  opt = attributes_to_define_after_schema_loads[name.to_s][1]
300
329
 
301
330
  has_default =
@@ -347,6 +376,26 @@ module Lockbox
347
376
  serialize name, Array
348
377
  end
349
378
  end
379
+ elsif ActiveRecord::VERSION::STRING.to_f >= 7.2
380
+ decorate_attributes([name]) do |attr_name, cast_type|
381
+ if cast_type.instance_of?(ActiveRecord::Type::Value)
382
+ original_type = pending_attribute_modifications.find { |v| v.is_a?(ActiveModel::AttributeRegistration::ClassMethods::PendingType) && v.name == original_name.to_s && !v.type.nil? }&.type
383
+ if original_type
384
+ original_type
385
+ elsif options[:migrating]
386
+ cast_type
387
+ else
388
+ ActiveRecord::Type::String.new
389
+ end
390
+ elsif cast_type.is_a?(ActiveRecord::Type::Serialized) && cast_type.subtype.instance_of?(ActiveModel::Type::Value)
391
+ # hack to set string type after serialize
392
+ # otherwise, type gets set to ActiveModel::Type::Value
393
+ # which always returns false for changed_in_place?
394
+ ActiveRecord::Type::Serialized.new(ActiveRecord::Type::String.new, cast_type.coder)
395
+ else
396
+ cast_type
397
+ end
398
+ end
350
399
  elsif !attributes_to_define_after_schema_loads.key?(name.to_s)
351
400
  # when migrating it's best to specify the type directly
352
401
  # however, we can try to use the original type if its already defined
@@ -360,8 +409,7 @@ module Lockbox
360
409
  attribute name, :string
361
410
  end
362
411
  else
363
- # hack for Active Record 6.1
364
- # to set string type after serialize
412
+ # hack for Active Record 6.1+ to set string type after serialize
365
413
  # otherwise, type gets set to ActiveModel::Type::Value
366
414
  # which always returns false for changed_in_place?
367
415
  # earlier versions of Active Record take the previous code path
@@ -447,12 +495,12 @@ module Lockbox
447
495
  # decrypt first for dirty tracking
448
496
  # don't raise error if can't decrypt previous
449
497
  # don't try to decrypt if no decryption key given
450
- unless options[:algorithm] == "hybrid" && options[:decryption_key].nil?
451
- begin
452
- send(name)
453
- rescue Lockbox::DecryptionError
454
- warn "[lockbox] Decrypting previous value failed"
455
- end
498
+ begin
499
+ send(name)
500
+ rescue Lockbox::DecryptionError
501
+ warn "[lockbox] Decrypting previous value failed"
502
+ rescue ArgumentError => e
503
+ raise e if e.message != "No decryption key set"
456
504
  end
457
505
 
458
506
  send("lockbox_direct_#{name}=", message)
@@ -669,7 +717,8 @@ module Lockbox
669
717
  end
670
718
 
671
719
  def lockbox_encrypts(*attributes, **options)
672
- ActiveSupport::Deprecation.warn("`#{__callee__}` is deprecated in favor of `has_encrypted`")
720
+ deprecator = ActiveSupport::VERSION::STRING.to_f >= 7.2 ? ActiveSupport.deprecator : ActiveSupport::Deprecation
721
+ deprecator.warn("`#{__callee__}` is deprecated in favor of `has_encrypted`")
673
722
  has_encrypted(*attributes, **options)
674
723
  end
675
724
 
@@ -1,3 +1,3 @@
1
1
  module Lockbox
2
- VERSION = "1.3.3"
2
+ VERSION = "1.4.1"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lockbox
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.3
4
+ version: 1.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-02-07 00:00:00.000000000 Z
11
+ date: 2024-09-09 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email: andrew@ankane.org
@@ -58,7 +58,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
58
58
  - !ruby/object:Gem::Version
59
59
  version: '0'
60
60
  requirements: []
61
- rubygems_version: 3.5.3
61
+ rubygems_version: 3.5.16
62
62
  signing_key:
63
63
  specification_version: 4
64
64
  summary: Modern encryption for Ruby and Rails