lockbox 1.3.2 → 1.4.0

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: 395cfb4c691d2ddb7280f6b0fe7e0e0bcc663c128a391751d6e2d03087c7c0f8
4
- data.tar.gz: 964271ba8bc79f94c940daf2f90c88da800a03560dde7870d9e69da67eff9e5a
3
+ metadata.gz: c227a11280d70eaaa03e7c81649bee696c2c88ab9a1825503f7db4e9af5d2d12
4
+ data.tar.gz: 52a2969568229aefa391a093c93ae1771d021ddc2396d3e4f65ec2f509a82c41
5
5
  SHA512:
6
- metadata.gz: 6833415a739d81b5570537616e8181a69d3b333d1f3ce3538e7a6f3f6c4a937611d07769d529f43c601bbf000c99cd55c89375b7a3be5915cb884df1785395ba
7
- data.tar.gz: eb64b479c6564db53d8b02821c987dc391c4c424e52de708f1a7b48308bd6ad381a8a72cd25f85cdbbd32b941f0e634efaa759269db764b9fba9efd3a0e0eea0
6
+ metadata.gz: 1734e1db6d559ed4221e81b30ca6a13db348a6089f58581376ce4cf16019a5787f8dd1b80abd1534ef2c82e294db4db1d8cb30089413d4e2e009544d3185d350
7
+ data.tar.gz: d5bdb1947c5fac19038fe21651bf35d32d675eba024569ec085b015ad991a9c1ea5e65437df908f78e0083486726032571c6dee364fe92a95a7447e33495cf83
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## 1.4.0 (2024-08-09)
2
+
3
+ - Added support for Active Record 7.2
4
+ - Added support for Mongoid 9
5
+ - Fixed error when `decryption_key` option is a proc or symbol and returns `nil`
6
+
7
+ ## 1.3.3 (2024-02-07)
8
+
9
+ - Added warning for encrypting store attributes
10
+
1
11
  ## 1.3.2 (2024-01-10)
2
12
 
3
13
  - Fixed issue with serialized attributes
data/README.md CHANGED
@@ -140,6 +140,8 @@ class User < ApplicationRecord
140
140
  end
141
141
  ```
142
142
 
143
+ For [Active Record Store](https://api.rubyonrails.org/classes/ActiveRecord/Store.html), encrypt the column rather than individual accessors.
144
+
143
145
  For [StoreModel](https://github.com/DmitryTsepelev/store_model), use:
144
146
 
145
147
  ```ruby
@@ -192,7 +194,7 @@ class User < ApplicationRecord
192
194
  has_encrypted :email
193
195
 
194
196
  # remove this line after dropping email column
195
- self.ignored_columns = ["email"]
197
+ self.ignored_columns += ["email"]
196
198
  end
197
199
  ```
198
200
 
@@ -1014,21 +1016,6 @@ class User < ApplicationRecord
1014
1016
  end
1015
1017
  ```
1016
1018
 
1017
- ### 0.6.0
1018
-
1019
- 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:
1020
-
1021
- ```ruby
1022
- User.with_attached_license.find_each do |user|
1023
- next unless user.license.attached?
1024
-
1025
- metadata = user.license.metadata
1026
- unless metadata["encrypted"]
1027
- user.license.blob.update!(metadata: metadata.merge("encrypted" => true))
1028
- end
1029
- end
1030
- ```
1031
-
1032
1019
  ## History
1033
1020
 
1034
1021
  View the [changelog](https://github.com/ankane/lockbox/blob/master/CHANGELOG.md)
@@ -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
271
+
272
+ # private
273
+ def self.lockbox_map_record_attributes(attributes, check_readonly: false)
274
+ return attributes unless attributes.is_a?(Hash)
256
275
 
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
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
@@ -289,8 +313,18 @@ module Lockbox
289
313
  @lockbox_attributes[original_name] = options
290
314
 
291
315
  if activerecord
316
+ # warn on store attributes
317
+ if stored_attributes.any? { |k, v| v.include?(name) }
318
+ warn "[lockbox] WARNING: encrypting store accessors is not supported. Encrypt the column instead."
319
+ end
320
+
292
321
  # warn on default attributes
293
- 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)
294
328
  opt = attributes_to_define_after_schema_loads[name.to_s][1]
295
329
 
296
330
  has_default =
@@ -342,6 +376,26 @@ module Lockbox
342
376
  serialize name, Array
343
377
  end
344
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
345
399
  elsif !attributes_to_define_after_schema_loads.key?(name.to_s)
346
400
  # when migrating it's best to specify the type directly
347
401
  # however, we can try to use the original type if its already defined
@@ -355,8 +409,7 @@ module Lockbox
355
409
  attribute name, :string
356
410
  end
357
411
  else
358
- # hack for Active Record 6.1
359
- # to set string type after serialize
412
+ # hack for Active Record 6.1+ to set string type after serialize
360
413
  # otherwise, type gets set to ActiveModel::Type::Value
361
414
  # which always returns false for changed_in_place?
362
415
  # earlier versions of Active Record take the previous code path
@@ -442,12 +495,12 @@ module Lockbox
442
495
  # decrypt first for dirty tracking
443
496
  # don't raise error if can't decrypt previous
444
497
  # don't try to decrypt if no decryption key given
445
- unless options[:algorithm] == "hybrid" && options[:decryption_key].nil?
446
- begin
447
- send(name)
448
- rescue Lockbox::DecryptionError
449
- warn "[lockbox] Decrypting previous value failed"
450
- 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"
451
504
  end
452
505
 
453
506
  send("lockbox_direct_#{name}=", message)
@@ -664,7 +717,8 @@ module Lockbox
664
717
  end
665
718
 
666
719
  def lockbox_encrypts(*attributes, **options)
667
- 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`")
668
722
  has_encrypted(*attributes, **options)
669
723
  end
670
724
 
@@ -1,3 +1,3 @@
1
1
  module Lockbox
2
- VERSION = "1.3.2"
2
+ VERSION = "1.4.0"
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.2
4
+ version: 1.4.0
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-01-10 00:00:00.000000000 Z
11
+ date: 2024-08-10 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.11
62
62
  signing_key:
63
63
  specification_version: 4
64
64
  summary: Modern encryption for Ruby and Rails