lockbox 0.6.2 → 0.6.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -1
- data/LICENSE.txt +1 -1
- data/README.md +21 -23
- data/lib/lockbox/carrier_wave_extensions.rb +4 -1
- data/lib/lockbox/model.rb +54 -10
- data/lib/lockbox/railtie.rb +8 -1
- data/lib/lockbox/version.rb +1 -1
- data/lib/lockbox.rb +7 -3
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0cc5589dbbfae34908ffdd634798549fa161c7dc79b528f23b8e097a044f3d9d
|
4
|
+
data.tar.gz: 3eb23c1dfb284abb335e2addfb20fee77b31373b561ab6c0f9afaf28abf8aeb8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b0bd8a7ee500cbba73daa2ef0c9c6af07695b53799735f865d5b0e9e907e8ef86904983d5f8cfdff098b554d4cd9d4351133778079bde93a7440bbacf03d0545
|
7
|
+
data.tar.gz: ea83a9ecb7734e907a03be89e628fc103815b0cc5acc2aec0c21dc0f2d6b7e746f299a489d4a11b14f413a55c4fb06b45d6e23302c853c8b216ad2bca63218d6
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,32 @@
|
|
1
|
-
## 0.6.
|
1
|
+
## 0.6.8 (2022-01-25)
|
2
|
+
|
3
|
+
- Fixed issue with `encrypts` loading model schema early
|
4
|
+
- Removed warning for attributes with `default` option
|
5
|
+
|
6
|
+
## 0.6.7 (2022-01-25)
|
7
|
+
|
8
|
+
- Added warning for attributes with `default` option
|
9
|
+
- Removed warning for Active Record 5.0 (still supported)
|
10
|
+
|
11
|
+
## 0.6.6 (2021-09-27)
|
12
|
+
|
13
|
+
- Fixed `attribute?` method for `boolean` and `integer` types
|
14
|
+
|
15
|
+
## 0.6.5 (2021-07-07)
|
16
|
+
|
17
|
+
- Fixed issue with `pluck` extension not loading in some cases
|
18
|
+
|
19
|
+
## 0.6.4 (2021-04-05)
|
20
|
+
|
21
|
+
- Fixed in place changes in callbacks
|
22
|
+
- Fixed `[]` method for encrypted attributes
|
23
|
+
|
24
|
+
## 0.6.3 (2021-03-30)
|
25
|
+
|
26
|
+
- Fixed empty arrays and hashes
|
27
|
+
- Fixed content type for CarrierWave 2.2.1
|
28
|
+
|
29
|
+
## 0.6.2 (2021-02-08)
|
2
30
|
|
3
31
|
- Added `inet` type
|
4
32
|
- Fixed error when `lockbox` key in Rails credentials has a string value
|
@@ -7,6 +35,7 @@
|
|
7
35
|
## 0.6.1 (2020-12-03)
|
8
36
|
|
9
37
|
- Added integration with Rails credentials
|
38
|
+
- Added warning for unsupported versions of Active Record
|
10
39
|
- Fixed in place changes for Active Record 6.1
|
11
40
|
- Fixed error with `content_type` method for CarrierWave < 2
|
12
41
|
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -16,7 +16,7 @@ Learn [the principles behind it](https://ankane.org/modern-encryption-rails), [h
|
|
16
16
|
Add this line to your application’s Gemfile:
|
17
17
|
|
18
18
|
```ruby
|
19
|
-
gem
|
19
|
+
gem "lockbox"
|
20
20
|
```
|
21
21
|
|
22
22
|
## Key Generation
|
@@ -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.
|
75
|
+
class AddEmailCiphertextToUsers < ActiveRecord::Migration[6.1]
|
76
76
|
def change
|
77
77
|
add_column :users, :email_ciphertext, :text
|
78
78
|
end
|
@@ -87,6 +87,8 @@ class User < ApplicationRecord
|
|
87
87
|
end
|
88
88
|
```
|
89
89
|
|
90
|
+
**Note:** With Rails 7, use `lockbox_encrypts` instead of `encrypts`
|
91
|
+
|
90
92
|
You can use `email` just like any other attribute.
|
91
93
|
|
92
94
|
```ruby
|
@@ -248,7 +250,7 @@ User.decrypt_email_ciphertext(user.email_ciphertext)
|
|
248
250
|
Create a migration with:
|
249
251
|
|
250
252
|
```ruby
|
251
|
-
class AddBodyCiphertextToRichTexts < ActiveRecord::Migration[6.
|
253
|
+
class AddBodyCiphertextToRichTexts < ActiveRecord::Migration[6.1]
|
252
254
|
def change
|
253
255
|
add_column :action_text_rich_texts, :body_ciphertext, :text
|
254
256
|
end
|
@@ -336,9 +338,9 @@ def license
|
|
336
338
|
end
|
337
339
|
```
|
338
340
|
|
339
|
-
|
341
|
+
Use `filename` to specify a filename or `disposition: "inline"` to show inline.
|
340
342
|
|
341
|
-
|
343
|
+
#### Migrating Existing Files
|
342
344
|
|
343
345
|
Lockbox makes it easy to encrypt existing files without downtime.
|
344
346
|
|
@@ -379,7 +381,7 @@ Encryption is applied to all versions after processing.
|
|
379
381
|
You can mount the uploader [as normal](https://github.com/carrierwaveuploader/carrierwave#activerecord). With Active Record, this involves creating a migration:
|
380
382
|
|
381
383
|
```ruby
|
382
|
-
class AddLicenseToUsers < ActiveRecord::Migration[6.
|
384
|
+
class AddLicenseToUsers < ActiveRecord::Migration[6.1]
|
383
385
|
def change
|
384
386
|
add_column :users, :license, :string
|
385
387
|
end
|
@@ -403,6 +405,8 @@ def license
|
|
403
405
|
end
|
404
406
|
```
|
405
407
|
|
408
|
+
Use `filename` to specify a filename or `disposition: "inline"` to show inline.
|
409
|
+
|
406
410
|
#### Migrating Existing Files
|
407
411
|
|
408
412
|
Encrypt existing files without downtime. Create a new encrypted uploader:
|
@@ -478,6 +482,8 @@ def license
|
|
478
482
|
end
|
479
483
|
```
|
480
484
|
|
485
|
+
Use `filename` to specify a filename or `disposition: "inline"` to show inline.
|
486
|
+
|
481
487
|
#### Non-Models
|
482
488
|
|
483
489
|
Generate a key
|
@@ -568,12 +574,10 @@ Update your model:
|
|
568
574
|
|
569
575
|
```ruby
|
570
576
|
class User < ApplicationRecord
|
571
|
-
encrypts :email, previous_versions: [{
|
577
|
+
encrypts :email, previous_versions: [{master_key: previous_key}]
|
572
578
|
end
|
573
579
|
```
|
574
580
|
|
575
|
-
Use `master_key` instead of `key` if passing the master key.
|
576
|
-
|
577
581
|
To rotate existing records, use:
|
578
582
|
|
579
583
|
```ruby
|
@@ -587,11 +591,9 @@ Once all records are rotated, you can remove `previous_versions` from the model.
|
|
587
591
|
Update your initializer:
|
588
592
|
|
589
593
|
```ruby
|
590
|
-
Lockbox.encrypts_action_text_body(previous_versions: [{
|
594
|
+
Lockbox.encrypts_action_text_body(previous_versions: [{master_key: previous_key}])
|
591
595
|
```
|
592
596
|
|
593
|
-
Use `master_key` instead of `key` if passing the master key.
|
594
|
-
|
595
597
|
To rotate existing records, use:
|
596
598
|
|
597
599
|
```ruby
|
@@ -606,12 +608,10 @@ Update your model:
|
|
606
608
|
|
607
609
|
```ruby
|
608
610
|
class User < ApplicationRecord
|
609
|
-
encrypts_attached :license, previous_versions: [{
|
611
|
+
encrypts_attached :license, previous_versions: [{master_key: previous_key}]
|
610
612
|
end
|
611
613
|
```
|
612
614
|
|
613
|
-
Use `master_key` instead of `key` if passing the master key.
|
614
|
-
|
615
615
|
To rotate existing files, use:
|
616
616
|
|
617
617
|
```ruby
|
@@ -628,12 +628,10 @@ Update your model:
|
|
628
628
|
|
629
629
|
```ruby
|
630
630
|
class LicenseUploader < CarrierWave::Uploader::Base
|
631
|
-
encrypt previous_versions: [{
|
631
|
+
encrypt previous_versions: [{master_key: previous_key}]
|
632
632
|
end
|
633
633
|
```
|
634
634
|
|
635
|
-
Use `master_key` instead of `key` if passing the master key.
|
636
|
-
|
637
635
|
To rotate existing files, use:
|
638
636
|
|
639
637
|
```ruby
|
@@ -708,7 +706,7 @@ This is the default algorithm. It’s:
|
|
708
706
|
|
709
707
|
Lockbox uses 256-bit keys.
|
710
708
|
|
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.
|
709
|
+
**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
710
|
|
713
711
|
### XSalsa20
|
714
712
|
|
@@ -721,7 +719,7 @@ brew install libsodium
|
|
721
719
|
And add to your Gemfile:
|
722
720
|
|
723
721
|
```ruby
|
724
|
-
gem
|
722
|
+
gem "rbnacl"
|
725
723
|
```
|
726
724
|
|
727
725
|
Then add to your model:
|
@@ -997,7 +995,7 @@ lockbox.decrypt(ciphertext, associated_data: "othercontext") # fails
|
|
997
995
|
You can use `binary` columns for the ciphertext instead of `text` columns.
|
998
996
|
|
999
997
|
```ruby
|
1000
|
-
class AddEmailCiphertextToUsers < ActiveRecord::Migration[6.
|
998
|
+
class AddEmailCiphertextToUsers < ActiveRecord::Migration[6.1]
|
1001
999
|
def change
|
1002
1000
|
add_column :users, :email_ciphertext, :binary
|
1003
1001
|
end
|
@@ -1042,7 +1040,7 @@ end
|
|
1042
1040
|
Create a migration with:
|
1043
1041
|
|
1044
1042
|
```ruby
|
1045
|
-
class MigrateToLockbox < ActiveRecord::Migration[6.
|
1043
|
+
class MigrateToLockbox < ActiveRecord::Migration[6.1]
|
1046
1044
|
def change
|
1047
1045
|
add_column :users, :name_ciphertext, :text
|
1048
1046
|
add_column :users, :email_ciphertext, :text
|
@@ -1075,7 +1073,7 @@ end
|
|
1075
1073
|
Then remove the previous gem from your Gemfile and drop its columns.
|
1076
1074
|
|
1077
1075
|
```ruby
|
1078
|
-
class RemovePreviousEncryptedColumns < ActiveRecord::Migration[6.
|
1076
|
+
class RemovePreviousEncryptedColumns < ActiveRecord::Migration[6.1]
|
1079
1077
|
def change
|
1080
1078
|
remove_column :users, :encrypted_name, :text
|
1081
1079
|
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
|
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
|
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
|
-
|
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
|
-
|
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 >=
|
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
|
-
|
324
|
-
|
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
|
-
|
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
|
-
|
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)
|
data/lib/lockbox/railtie.rb
CHANGED
@@ -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 >=
|
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
|
data/lib/lockbox/version.rb
CHANGED
data/lib/lockbox.rb
CHANGED
@@ -28,17 +28,21 @@ end
|
|
28
28
|
if defined?(ActiveSupport.on_load)
|
29
29
|
ActiveSupport.on_load(:active_record) do
|
30
30
|
# TODO raise error in 0.7.0
|
31
|
-
if ActiveRecord::VERSION::STRING.to_f
|
31
|
+
if ActiveRecord::VERSION::STRING.to_f < 5.0
|
32
32
|
warn "Active Record version (#{ActiveRecord::VERSION::STRING}) not supported in this version of Lockbox (#{Lockbox::VERSION})"
|
33
33
|
end
|
34
34
|
|
35
35
|
extend Lockbox::Model
|
36
36
|
extend Lockbox::Model::Attached
|
37
|
-
|
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.
|
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.
|
4
|
+
version: 0.6.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-01-26 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.
|
61
|
+
rubygems_version: 3.3.3
|
62
62
|
signing_key:
|
63
63
|
specification_version: 4
|
64
64
|
summary: Modern encryption for Ruby and Rails
|