lockbox 0.6.2 → 0.6.6

Sign up to get free protection for your applications and to get access to all the features.
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