lockbox 0.6.2 → 0.6.6

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: 4ba37bc916c02e18555640f29e47483898a96e04b75639b49ce9a0003fbaa443
4
- data.tar.gz: 750a53ca3201e51b6dc0221305d4c021d64716a163a2e0cfc98c11ec8d1b6af8
3
+ metadata.gz: 71efd55022975063c04c0a0588532e1bdd5167c7f2e6df5c8cf442362b4d52d6
4
+ data.tar.gz: b823faa4b637c300367032b625972f7d41d0e3e12595921d7474140a955c64c7
5
5
  SHA512:
6
- metadata.gz: b4e23752c311bf6b161e6817ab204ba0b5936594db5c2509c3f5ef6e354c169097a1c799d7b5147daa0d68982f072d1d22363019c6881566403517f97a5f2c45
7
- data.tar.gz: 51ab913facdc34aea3e3ef263dab2fa5c3852aa052de80804ec29019c2517df9244eefb8c15e6f2c1ca0059bfd4f9cc020ce00fff543874f6461faeec1e78443
6
+ metadata.gz: 0d7fd7b285c51ac3f80657ec8956d7cd4692481d33710be69dfa567da02dd2f38de7bef9e4b43962395d9241d7d8c452d01f156e2124d957f52ea9026c0ea14f
7
+ data.tar.gz: 5d62ccbb565e9a3a6e76c1b6df617377bdcd1003289634cd16a2ba4b0e1d1a1ec72966c2a98dae27959e471fdf9ca77fd0755d25bd75ce6bd1e0cd7755819162
data/CHANGELOG.md CHANGED
@@ -1,4 +1,22 @@
1
- ## 0.6.2 (2020-02-08)
1
+ ## 0.6.6 (2021-09-27)
2
+
3
+ - Fixed `attribute?` method for `boolean` and `integer` types
4
+
5
+ ## 0.6.5 (2021-07-07)
6
+
7
+ - Fixed issue with `pluck` extension not loading in some cases
8
+
9
+ ## 0.6.4 (2021-04-05)
10
+
11
+ - Fixed in place changes in callbacks
12
+ - Fixed `[]` method for encrypted attributes
13
+
14
+ ## 0.6.3 (2021-03-30)
15
+
16
+ - Fixed empty arrays and hashes
17
+ - Fixed content type for CarrierWave 2.2.1
18
+
19
+ ## 0.6.2 (2021-02-08)
2
20
 
3
21
  - Added `inet` type
4
22
  - Fixed error when `lockbox` key in Rails credentials has a string value
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[6.0]
75
+ class AddEmailCiphertextToUsers < ActiveRecord::Migration[6.1]
76
76
  def change
77
77
  add_column :users, :email_ciphertext, :text
78
78
  end
@@ -248,7 +248,7 @@ User.decrypt_email_ciphertext(user.email_ciphertext)
248
248
  Create a migration with:
249
249
 
250
250
  ```ruby
251
- class AddBodyCiphertextToRichTexts < ActiveRecord::Migration[6.0]
251
+ class AddBodyCiphertextToRichTexts < ActiveRecord::Migration[6.1]
252
252
  def change
253
253
  add_column :action_text_rich_texts, :body_ciphertext, :text
254
254
  end
@@ -336,9 +336,7 @@ def license
336
336
  end
337
337
  ```
338
338
 
339
- #### Migrating Existing Files [experimental]
340
-
341
- **Note:** This feature is experimental. Please try it in a non-production environment and [share](https://github.com/ankane/lockbox/issues/44) how it goes.
339
+ #### Migrating Existing Files
342
340
 
343
341
  Lockbox makes it easy to encrypt existing files without downtime.
344
342
 
@@ -379,7 +377,7 @@ Encryption is applied to all versions after processing.
379
377
  You can mount the uploader [as normal](https://github.com/carrierwaveuploader/carrierwave#activerecord). With Active Record, this involves creating a migration:
380
378
 
381
379
  ```ruby
382
- class AddLicenseToUsers < ActiveRecord::Migration[6.0]
380
+ class AddLicenseToUsers < ActiveRecord::Migration[6.1]
383
381
  def change
384
382
  add_column :users, :license, :string
385
383
  end
@@ -568,12 +566,10 @@ Update your model:
568
566
 
569
567
  ```ruby
570
568
  class User < ApplicationRecord
571
- encrypts :email, previous_versions: [{key: previous_key}]
569
+ encrypts :email, previous_versions: [{master_key: previous_key}]
572
570
  end
573
571
  ```
574
572
 
575
- Use `master_key` instead of `key` if passing the master key.
576
-
577
573
  To rotate existing records, use:
578
574
 
579
575
  ```ruby
@@ -587,11 +583,9 @@ Once all records are rotated, you can remove `previous_versions` from the model.
587
583
  Update your initializer:
588
584
 
589
585
  ```ruby
590
- Lockbox.encrypts_action_text_body(previous_versions: [{key: previous_key}])
586
+ Lockbox.encrypts_action_text_body(previous_versions: [{master_key: previous_key}])
591
587
  ```
592
588
 
593
- Use `master_key` instead of `key` if passing the master key.
594
-
595
589
  To rotate existing records, use:
596
590
 
597
591
  ```ruby
@@ -606,12 +600,10 @@ Update your model:
606
600
 
607
601
  ```ruby
608
602
  class User < ApplicationRecord
609
- encrypts_attached :license, previous_versions: [{key: previous_key}]
603
+ encrypts_attached :license, previous_versions: [{master_key: previous_key}]
610
604
  end
611
605
  ```
612
606
 
613
- Use `master_key` instead of `key` if passing the master key.
614
-
615
607
  To rotate existing files, use:
616
608
 
617
609
  ```ruby
@@ -628,12 +620,10 @@ Update your model:
628
620
 
629
621
  ```ruby
630
622
  class LicenseUploader < CarrierWave::Uploader::Base
631
- encrypt previous_versions: [{key: previous_key}]
623
+ encrypt previous_versions: [{master_key: previous_key}]
632
624
  end
633
625
  ```
634
626
 
635
- Use `master_key` instead of `key` if passing the master key.
636
-
637
627
  To rotate existing files, use:
638
628
 
639
629
  ```ruby
@@ -708,7 +698,7 @@ This is the default algorithm. It’s:
708
698
 
709
699
  Lockbox uses 256-bit keys.
710
700
 
711
- **For users who do a lot of encryptions:** You should rotate an individual key after 2 billion encryptions to minimize the chance of a [nonce collision](https://www.cryptologie.net/article/402/is-symmetric-security-solved/), which will expose the key. Each database field and file uploader use a different key (derived from the master key) to extend this window.
701
+ **For users who do a lot of encryptions:** You should rotate an individual key after 2 billion encryptions to minimize the chance of a [nonce collision](https://www.cryptologie.net/article/402/is-symmetric-security-solved/), which will expose the authentication key. Each database field and file uploader use a different key (derived from the master key) to extend this window.
712
702
 
713
703
  ### XSalsa20
714
704
 
@@ -997,7 +987,7 @@ lockbox.decrypt(ciphertext, associated_data: "othercontext") # fails
997
987
  You can use `binary` columns for the ciphertext instead of `text` columns.
998
988
 
999
989
  ```ruby
1000
- class AddEmailCiphertextToUsers < ActiveRecord::Migration[6.0]
990
+ class AddEmailCiphertextToUsers < ActiveRecord::Migration[6.1]
1001
991
  def change
1002
992
  add_column :users, :email_ciphertext, :binary
1003
993
  end
@@ -1042,7 +1032,7 @@ end
1042
1032
  Create a migration with:
1043
1033
 
1044
1034
  ```ruby
1045
- class MigrateToLockbox < ActiveRecord::Migration[6.0]
1035
+ class MigrateToLockbox < ActiveRecord::Migration[6.1]
1046
1036
  def change
1047
1037
  add_column :users, :name_ciphertext, :text
1048
1038
  add_column :users, :email_ciphertext, :text
@@ -1075,7 +1065,7 @@ end
1075
1065
  Then remove the previous gem from your Gemfile and drop its columns.
1076
1066
 
1077
1067
  ```ruby
1078
- class RemovePreviousEncryptedColumns < ActiveRecord::Migration[6.0]
1068
+ class RemovePreviousEncryptedColumns < ActiveRecord::Migration[6.1]
1079
1069
  def change
1080
1070
  remove_column :users, :encrypted_name, :text
1081
1071
  remove_column :users, :encrypted_name_iv, :text
@@ -33,7 +33,10 @@ module Lockbox
33
33
  end
34
34
 
35
35
  def content_type
36
- if CarrierWave::VERSION.to_i >= 2
36
+ if Gem::Version.new(CarrierWave::VERSION) >= Gem::Version.new("2.2.1")
37
+ # based on CarrierWave::SanitizedFile#marcel_magic_content_type
38
+ Marcel::Magic.by_magic(read).try(:type) || "invalid/invalid"
39
+ elsif CarrierWave::VERSION.to_i >= 2
37
40
  # based on CarrierWave::SanitizedFile#mime_magic_content_type
38
41
  MimeMagic.by_magic(read).try(:type) || "invalid/invalid"
39
42
  else
data/lib/lockbox/model.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module Lockbox
2
2
  module Model
3
- def encrypts(*attributes, **options)
3
+ def lockbox_encrypts(*attributes, **options)
4
4
  # support objects
5
5
  # case options[:type]
6
6
  # when Date
@@ -149,16 +149,38 @@ module Lockbox
149
149
  # needed for in-place modifications
150
150
  # assigned attributes are encrypted on assignment
151
151
  # and then again here
152
- before_save do
152
+ def lockbox_sync_attributes
153
153
  self.class.lockbox_attributes.each do |_, lockbox_attribute|
154
154
  attribute = lockbox_attribute[:attribute]
155
155
 
156
- if attribute_changed_in_place?(attribute)
156
+ if attribute_changed_in_place?(attribute) || (send("#{attribute}_changed?") && !send("#{lockbox_attribute[:encrypted_attribute]}_changed?"))
157
157
  send("#{attribute}=", send(attribute))
158
158
  end
159
159
  end
160
160
  end
161
161
 
162
+ # safety check
163
+ [:_create_record, :_update_record].each do |method_name|
164
+ unless private_method_defined?(method_name) || method_defined?(method_name)
165
+ raise Lockbox::Error, "Expected #{method_name} to be defined. Please report an issue."
166
+ end
167
+ end
168
+
169
+ def _create_record(*)
170
+ lockbox_sync_attributes
171
+ super
172
+ end
173
+
174
+ def _update_record(*)
175
+ lockbox_sync_attributes
176
+ super
177
+ end
178
+
179
+ def [](attr_name)
180
+ send(attr_name) if self.class.lockbox_attributes.any? { |_, la| la[:attribute] == attr_name.to_s }
181
+ super
182
+ end
183
+
162
184
  def update_columns(attributes)
163
185
  return super unless attributes.is_a?(Hash)
164
186
 
@@ -194,8 +216,11 @@ module Lockbox
194
216
  attributes_to_set.each do |k, v|
195
217
  if respond_to?(:write_attribute_without_type_cast, true)
196
218
  write_attribute_without_type_cast(k, v)
197
- else
219
+ elsif respond_to?(:raw_write_attribute, true)
198
220
  raw_write_attribute(k, v)
221
+ else
222
+ @attributes.write_cast_value(k, v)
223
+ clear_attribute_change(k)
199
224
  end
200
225
  end
201
226
 
@@ -254,7 +279,12 @@ module Lockbox
254
279
  # otherwise, type gets set to ActiveModel::Type::Value
255
280
  # which always returns false for changed_in_place?
256
281
  # earlier versions of Active Record take the previous code path
257
- if ActiveRecord::VERSION::STRING.to_f >= 6.1 && attributes_to_define_after_schema_loads[name.to_s].first.is_a?(Proc)
282
+ if ActiveRecord::VERSION::STRING.to_f >= 7.0 && attributes_to_define_after_schema_loads[name.to_s].first.is_a?(Proc)
283
+ attribute_type = attributes_to_define_after_schema_loads[name.to_s].first.call(nil)
284
+ if attribute_type.is_a?(ActiveRecord::Type::Serialized) && attribute_type.subtype.nil?
285
+ attribute name, ActiveRecord::Type::Serialized.new(ActiveRecord::Type::String.new, attribute_type.coder)
286
+ end
287
+ elsif ActiveRecord::VERSION::STRING.to_f >= 6.1 && attributes_to_define_after_schema_loads[name.to_s].first.is_a?(Proc)
258
288
  attribute_type = attributes_to_define_after_schema_loads[name.to_s].first.call
259
289
  if attribute_type.is_a?(ActiveRecord::Type::Serialized) && attribute_type.subtype.nil?
260
290
  attribute name, ActiveRecord::Type::Serialized.new(ActiveRecord::Type::String.new, attribute_type.coder)
@@ -279,6 +309,11 @@ module Lockbox
279
309
  super()
280
310
  end
281
311
  end
312
+
313
+ define_method("#{name}?") do
314
+ # uses public_send, so we don't need to preload attribute
315
+ query_attribute(name)
316
+ end
282
317
  else
283
318
  # keep this module dead simple
284
319
  # Mongoid uses changed_attributes to calculate keys to update
@@ -318,10 +353,10 @@ module Lockbox
318
353
  send("reset_#{encrypted_attribute}_to_default!")
319
354
  send(name)
320
355
  end
321
- end
322
356
 
323
- define_method("#{name}?") do
324
- send("#{encrypted_attribute}?")
357
+ define_method("#{name}?") do
358
+ send("#{encrypted_attribute}?")
359
+ end
325
360
  end
326
361
 
327
362
  define_method("#{name}=") do |message|
@@ -371,7 +406,11 @@ module Lockbox
371
406
  # check for this explicitly as a layer of safety
372
407
  if message.nil? || ((message == {} || message == []) && activerecord && @attributes[name.to_s].value_before_type_cast.nil?)
373
408
  ciphertext = send(encrypted_attribute)
374
- message = self.class.send(decrypt_method_name, ciphertext, context: self)
409
+
410
+ # keep original message for empty hashes and arrays
411
+ unless ciphertext.nil?
412
+ message = self.class.send(decrypt_method_name, ciphertext, context: self)
413
+ end
375
414
 
376
415
  if activerecord
377
416
  # set previous attribute so changes populate correctly
@@ -383,8 +422,13 @@ module Lockbox
383
422
  # decrypt method does type casting
384
423
  if respond_to?(:write_attribute_without_type_cast, true)
385
424
  write_attribute_without_type_cast(name.to_s, message) if !@attributes.frozen?
386
- else
425
+ elsif respond_to?(:raw_write_attribute, true)
387
426
  raw_write_attribute(name, message) if !@attributes.frozen?
427
+ else
428
+ if !@attributes.frozen?
429
+ @attributes.write_cast_value(name.to_s, message)
430
+ clear_attribute_change(name)
431
+ end
388
432
  end
389
433
  else
390
434
  instance_variable_set("@#{name}", message)
@@ -19,7 +19,14 @@ module Lockbox
19
19
  ActiveStorage::Attached::Many.prepend(Lockbox::ActiveStorageExtensions::AttachedMany)
20
20
 
21
21
  # use load hooks when possible
22
- if ActiveStorage::VERSION::MAJOR >= 6
22
+ if ActiveStorage::VERSION::MAJOR >= 7
23
+ ActiveSupport.on_load(:active_storage_attachment) do
24
+ prepend Lockbox::ActiveStorageExtensions::Attachment
25
+ end
26
+ ActiveSupport.on_load(:active_storage_blob) do
27
+ prepend Lockbox::ActiveStorageExtensions::Blob
28
+ end
29
+ elsif ActiveStorage::VERSION::MAJOR >= 6
23
30
  ActiveSupport.on_load(:active_storage_attachment) do
24
31
  include Lockbox::ActiveStorageExtensions::Attachment
25
32
  end
@@ -1,3 +1,3 @@
1
1
  module Lockbox
2
- VERSION = "0.6.2"
2
+ VERSION = "0.6.6"
3
3
  end
data/lib/lockbox.rb CHANGED
@@ -34,11 +34,15 @@ if defined?(ActiveSupport.on_load)
34
34
 
35
35
  extend Lockbox::Model
36
36
  extend Lockbox::Model::Attached
37
- ActiveRecord::Calculations.prepend Lockbox::Calculations
37
+ # alias_method is private in Ruby < 2.5
38
+ singleton_class.send(:alias_method, :encrypts, :lockbox_encrypts) if ActiveRecord::VERSION::MAJOR < 7
39
+ ActiveRecord::Relation.prepend Lockbox::Calculations
38
40
  end
39
41
 
40
42
  ActiveSupport.on_load(:mongoid) do
41
43
  Mongoid::Document::ClassMethods.include(Lockbox::Model)
44
+ # alias_method is private in Ruby < 2.5
45
+ Mongoid::Document::ClassMethods.send(:alias_method, :encrypts, :lockbox_encrypts)
42
46
  end
43
47
  end
44
48
 
@@ -106,7 +110,7 @@ module Lockbox
106
110
 
107
111
  def self.encrypts_action_text_body(**options)
108
112
  ActiveSupport.on_load(:action_text_rich_text) do
109
- ActionText::RichText.encrypts :body, **options
113
+ ActionText::RichText.lockbox_encrypts :body, **options
110
114
  end
111
115
  end
112
116
  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: 0.6.2
4
+ version: 0.6.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-02-08 00:00:00.000000000 Z
11
+ date: 2021-09-28 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.2.3
61
+ rubygems_version: 3.2.22
62
62
  signing_key:
63
63
  specification_version: 4
64
64
  summary: Modern encryption for Ruby and Rails