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 +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
|