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 +4 -4
- data/CHANGELOG.md +19 -1
- data/README.md +12 -22
- 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 +6 -2
- 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: 71efd55022975063c04c0a0588532e1bdd5167c7f2e6df5c8cf442362b4d52d6
|
|
4
|
+
data.tar.gz: b823faa4b637c300367032b625972f7d41d0e3e12595921d7474140a955c64c7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0d7fd7b285c51ac3f80657ec8956d7cd4692481d33710be69dfa567da02dd2f38de7bef9e4b43962395d9241d7d8c452d01f156e2124d957f52ea9026c0ea14f
|
|
7
|
+
data.tar.gz: 5d62ccbb565e9a3a6e76c1b6df617377bdcd1003289634cd16a2ba4b0e1d1a1ec72966c2a98dae27959e471fdf9ca77fd0755d25bd75ce6bd1e0cd7755819162
|
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
|
-
## 0.6.
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
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: [{
|
|
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: [{
|
|
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: [{
|
|
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: [{
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
@@ -34,11 +34,15 @@ if defined?(ActiveSupport.on_load)
|
|
|
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.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-
|
|
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.
|
|
61
|
+
rubygems_version: 3.2.22
|
|
62
62
|
signing_key:
|
|
63
63
|
specification_version: 4
|
|
64
64
|
summary: Modern encryption for Ruby and Rails
|