lockbox 1.4.0 → 2.1.0
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 +21 -0
- data/LICENSE.txt +1 -1
- data/README.md +7 -19
- data/lib/generators/lockbox/audits_generator.rb +1 -5
- data/lib/lockbox/active_storage_extensions.rb +49 -44
- data/lib/lockbox/encryptor.rb +2 -2
- data/lib/lockbox/model.rb +97 -110
- data/lib/lockbox/railtie.rb +6 -23
- data/lib/lockbox/version.rb +1 -1
- data/lib/lockbox.rb +15 -5
- metadata +4 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 732b29630e94d7e05292a10ff4106f05152418a335fb7a5e7da9b5a632c05856
|
|
4
|
+
data.tar.gz: 8d7d40d7d4f8bb5ecc4737c4664a87bfc33d1f331b0a46d36ebc6a3ce5573ee2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 11ba243e0e997a3140a5f3682f38023b9dcf29fdd9c181ea9f693404b4204bd7e83a4caef669126ff9fe432882773ff8f52e129117c201f0dc6eae7de9539849
|
|
7
|
+
data.tar.gz: 206197fbff415597cb816f77d9354fc8b2ee0c60ceccfb1b5414a44cf19a14b1b9224e09d1c7b0148a9ecb4b0d6673f70f2f35008b6a105bab8e02a55fdf81ce
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
## 2.1.0 (2025-10-15)
|
|
2
|
+
|
|
3
|
+
- Added warning for `download_chunk` method
|
|
4
|
+
- Fixed error for `download` method with block
|
|
5
|
+
- Dropped support for Active Record < 7.1 and Ruby < 3.2
|
|
6
|
+
|
|
7
|
+
## 2.0.1 (2024-12-29)
|
|
8
|
+
|
|
9
|
+
- Added support for Ruby 3.4
|
|
10
|
+
|
|
11
|
+
## 2.0.0 (2024-10-26)
|
|
12
|
+
|
|
13
|
+
- Improved `attributes`, `attribute_names`, and `has_attribute?` when ciphertext attributes not loaded
|
|
14
|
+
- Removed deprecated `lockbox_encrypts` (use `has_encrypted` instead)
|
|
15
|
+
- Dropped support for Active Record < 7 and Ruby < 3.1
|
|
16
|
+
- Dropped support for Mongoid < 8
|
|
17
|
+
|
|
18
|
+
## 1.4.1 (2024-09-09)
|
|
19
|
+
|
|
20
|
+
- Fixed error message for previews for Active Storage 7.1.4
|
|
21
|
+
|
|
1
22
|
## 1.4.0 (2024-08-09)
|
|
2
23
|
|
|
3
24
|
- Added support for Active Record 7.2
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
|
@@ -35,7 +35,7 @@ Set the following environment variable with your key (you can use this one in de
|
|
|
35
35
|
LOCKBOX_MASTER_KEY=0000000000000000000000000000000000000000000000000000000000000000
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
or add it to your credentials for each environment (`rails credentials:edit --environment <env>`
|
|
38
|
+
or add it to your credentials for each environment (`rails credentials:edit --environment <env>`)
|
|
39
39
|
|
|
40
40
|
```yml
|
|
41
41
|
lockbox:
|
|
@@ -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[
|
|
75
|
+
class AddEmailCiphertextToUsers < ActiveRecord::Migration[8.0]
|
|
76
76
|
def change
|
|
77
77
|
add_column :users, :email_ciphertext, :text
|
|
78
78
|
end
|
|
@@ -251,7 +251,7 @@ User.decrypt_email_ciphertext(user.email_ciphertext)
|
|
|
251
251
|
Create a migration with:
|
|
252
252
|
|
|
253
253
|
```ruby
|
|
254
|
-
class AddBodyCiphertextToRichTexts < ActiveRecord::Migration[
|
|
254
|
+
class AddBodyCiphertextToRichTexts < ActiveRecord::Migration[8.0]
|
|
255
255
|
def change
|
|
256
256
|
add_column :action_text_rich_texts, :body_ciphertext, :text
|
|
257
257
|
end
|
|
@@ -382,7 +382,7 @@ Encryption is applied to all versions after processing.
|
|
|
382
382
|
You can mount the uploader [as normal](https://github.com/carrierwaveuploader/carrierwave#activerecord). With Active Record, this involves creating a migration:
|
|
383
383
|
|
|
384
384
|
```ruby
|
|
385
|
-
class AddLicenseToUsers < ActiveRecord::Migration[
|
|
385
|
+
class AddLicenseToUsers < ActiveRecord::Migration[8.0]
|
|
386
386
|
def change
|
|
387
387
|
add_column :users, :license, :string
|
|
388
388
|
end
|
|
@@ -910,7 +910,7 @@ end
|
|
|
910
910
|
You can use `binary` columns for the ciphertext instead of `text` columns.
|
|
911
911
|
|
|
912
912
|
```ruby
|
|
913
|
-
class AddEmailCiphertextToUsers < ActiveRecord::Migration[
|
|
913
|
+
class AddEmailCiphertextToUsers < ActiveRecord::Migration[8.0]
|
|
914
914
|
def change
|
|
915
915
|
add_column :users, :email_ciphertext, :binary
|
|
916
916
|
end
|
|
@@ -961,7 +961,7 @@ end
|
|
|
961
961
|
Create a migration with:
|
|
962
962
|
|
|
963
963
|
```ruby
|
|
964
|
-
class MigrateToLockbox < ActiveRecord::Migration[
|
|
964
|
+
class MigrateToLockbox < ActiveRecord::Migration[8.0]
|
|
965
965
|
def change
|
|
966
966
|
add_column :users, :name_ciphertext, :text
|
|
967
967
|
add_column :users, :email_ciphertext, :text
|
|
@@ -994,7 +994,7 @@ end
|
|
|
994
994
|
Then remove the previous gem from your Gemfile and drop its columns.
|
|
995
995
|
|
|
996
996
|
```ruby
|
|
997
|
-
class RemovePreviousEncryptedColumns < ActiveRecord::Migration[
|
|
997
|
+
class RemovePreviousEncryptedColumns < ActiveRecord::Migration[8.0]
|
|
998
998
|
def change
|
|
999
999
|
remove_column :users, :encrypted_name, :text
|
|
1000
1000
|
remove_column :users, :encrypted_name_iv, :text
|
|
@@ -1004,18 +1004,6 @@ class RemovePreviousEncryptedColumns < ActiveRecord::Migration[7.1]
|
|
|
1004
1004
|
end
|
|
1005
1005
|
```
|
|
1006
1006
|
|
|
1007
|
-
## Upgrading
|
|
1008
|
-
|
|
1009
|
-
### 1.0.0
|
|
1010
|
-
|
|
1011
|
-
`encrypts` is now deprecated in favor of `has_encrypted` to avoid conflicting with Active Record encryption.
|
|
1012
|
-
|
|
1013
|
-
```ruby
|
|
1014
|
-
class User < ApplicationRecord
|
|
1015
|
-
has_encrypted :email
|
|
1016
|
-
end
|
|
1017
|
-
```
|
|
1018
|
-
|
|
1019
1007
|
## History
|
|
1020
1008
|
|
|
1021
1009
|
View the [changelog](https://github.com/ankane/lockbox/blob/master/CHANGELOG.md)
|
|
@@ -29,11 +29,7 @@ module Lockbox
|
|
|
29
29
|
# use connection_config instead of connection.adapter
|
|
30
30
|
# so database connection isn't needed
|
|
31
31
|
def adapter
|
|
32
|
-
|
|
33
|
-
ActiveRecord::Base.connection_db_config.adapter.to_s
|
|
34
|
-
else
|
|
35
|
-
ActiveRecord::Base.connection_config[:adapter].to_s
|
|
36
|
-
end
|
|
32
|
+
ActiveRecord::Base.connection_db_config.adapter.to_s
|
|
37
33
|
end
|
|
38
34
|
end
|
|
39
35
|
end
|
|
@@ -34,13 +34,6 @@ module Lockbox
|
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
module AttachedOne
|
|
37
|
-
if ActiveStorage::VERSION::MAJOR < 6
|
|
38
|
-
def attach(attachable)
|
|
39
|
-
attachable = encrypt_attachable(attachable) if encrypted?
|
|
40
|
-
super(attachable)
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|
|
44
37
|
def rotate_encryption!
|
|
45
38
|
raise "Not encrypted" unless encrypted?
|
|
46
39
|
|
|
@@ -51,19 +44,6 @@ module Lockbox
|
|
|
51
44
|
end
|
|
52
45
|
|
|
53
46
|
module AttachedMany
|
|
54
|
-
if ActiveStorage::VERSION::MAJOR < 6
|
|
55
|
-
def attach(*attachables)
|
|
56
|
-
if encrypted?
|
|
57
|
-
attachables =
|
|
58
|
-
attachables.flatten.collect do |attachable|
|
|
59
|
-
encrypt_attachable(attachable)
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
super(attachables)
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
|
|
67
47
|
def rotate_encryption!
|
|
68
48
|
raise "Not encrypted" unless encrypted?
|
|
69
49
|
|
|
@@ -100,7 +80,7 @@ module Lockbox
|
|
|
100
80
|
|
|
101
81
|
module Attachment
|
|
102
82
|
def download
|
|
103
|
-
result = super
|
|
83
|
+
result = super(&nil)
|
|
104
84
|
|
|
105
85
|
options = Utils.encrypted_options(record, name)
|
|
106
86
|
# only trust the metadata when migrating
|
|
@@ -111,45 +91,70 @@ module Lockbox
|
|
|
111
91
|
result = Utils.decrypt_result(record, name, options, result)
|
|
112
92
|
end
|
|
113
93
|
|
|
114
|
-
|
|
94
|
+
if block_given?
|
|
95
|
+
io = StringIO.new(result)
|
|
96
|
+
chunk_size = 5.megabytes
|
|
97
|
+
while (chunk = io.read(chunk_size))
|
|
98
|
+
yield chunk
|
|
99
|
+
end
|
|
100
|
+
else
|
|
101
|
+
result
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def download_chunk(...)
|
|
106
|
+
# TODO raise error in 3.0
|
|
107
|
+
warn "[lockbox] WARNING: download_chunk not supported for encrypted files" if Utils.encrypted_options(record, name)
|
|
108
|
+
super
|
|
115
109
|
end
|
|
116
110
|
|
|
117
|
-
def variant(
|
|
111
|
+
def variant(...)
|
|
118
112
|
raise Lockbox::Error, "Variant not supported for encrypted files" if Utils.encrypted_options(record, name)
|
|
119
113
|
super
|
|
120
114
|
end
|
|
121
115
|
|
|
122
|
-
def preview(
|
|
116
|
+
def preview(...)
|
|
123
117
|
raise Lockbox::Error, "Preview not supported for encrypted files" if Utils.encrypted_options(record, name)
|
|
124
118
|
super
|
|
125
119
|
end
|
|
126
120
|
|
|
127
|
-
if ActiveStorage::VERSION::
|
|
128
|
-
def
|
|
129
|
-
blob.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
121
|
+
if ActiveStorage::VERSION::STRING.to_f == 7.1 && ActiveStorage.version >= "7.1.4"
|
|
122
|
+
def transform_variants_later
|
|
123
|
+
blob.instance_variable_set(:@lockbox_encrypted, true) if Utils.encrypted_options(record, name)
|
|
124
|
+
super
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def open(**options)
|
|
129
|
+
blob.open(**options) do |file|
|
|
130
|
+
options = Utils.encrypted_options(record, name)
|
|
131
|
+
# only trust the metadata when migrating
|
|
132
|
+
# as earlier versions of Lockbox won't have it
|
|
133
|
+
# and it's not a good practice to trust modifiable data
|
|
134
|
+
encrypted = options && (!options[:migrating] || blob.metadata["encrypted"])
|
|
135
|
+
if encrypted
|
|
136
|
+
result = Utils.decrypt_result(record, name, options, file.read)
|
|
137
|
+
file.rewind
|
|
138
|
+
# truncate may not be available on all platforms
|
|
139
|
+
# according to the Ruby docs
|
|
140
|
+
# may need to create a new temp file instead
|
|
141
|
+
file.truncate(0)
|
|
142
|
+
file.write(result)
|
|
143
|
+
file.rewind
|
|
147
144
|
end
|
|
145
|
+
|
|
146
|
+
yield file
|
|
148
147
|
end
|
|
149
148
|
end
|
|
150
149
|
end
|
|
151
150
|
|
|
152
151
|
module Blob
|
|
152
|
+
if ActiveStorage::VERSION::STRING.to_f == 7.1 && ActiveStorage.version >= "7.1.4"
|
|
153
|
+
def preview_image_needed_before_processing_variants?
|
|
154
|
+
!instance_variable_defined?(:@lockbox_encrypted) && super
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
153
158
|
private
|
|
154
159
|
|
|
155
160
|
def extract_content_type(io)
|
data/lib/lockbox/encryptor.rb
CHANGED
|
@@ -15,12 +15,12 @@ module Lockbox
|
|
|
15
15
|
def encrypt(message, **options)
|
|
16
16
|
message = check_string(message)
|
|
17
17
|
ciphertext = @boxes.first.encrypt(message, **options)
|
|
18
|
-
ciphertext =
|
|
18
|
+
ciphertext = [ciphertext].pack("m0") if @encode
|
|
19
19
|
ciphertext
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def decrypt(ciphertext, **options)
|
|
23
|
-
ciphertext =
|
|
23
|
+
ciphertext = ciphertext.unpack1("m") if @encode
|
|
24
24
|
ciphertext = check_string(ciphertext)
|
|
25
25
|
|
|
26
26
|
# ensure binary
|
data/lib/lockbox/model.rb
CHANGED
|
@@ -60,7 +60,7 @@ module Lockbox
|
|
|
60
60
|
class_eval do
|
|
61
61
|
# Lockbox uses custom inspect
|
|
62
62
|
# but this could be useful for other gems
|
|
63
|
-
if activerecord
|
|
63
|
+
if activerecord
|
|
64
64
|
# only add virtual attribute
|
|
65
65
|
# need to use regexp since strings do partial matching
|
|
66
66
|
# also, need to use += instead of <<
|
|
@@ -114,12 +114,6 @@ module Lockbox
|
|
|
114
114
|
k = lockbox_encrypted_attributes[k]
|
|
115
115
|
elsif values.key?(k)
|
|
116
116
|
v = respond_to?(:attribute_for_inspect) ? attribute_for_inspect(k) : values[k].inspect
|
|
117
|
-
|
|
118
|
-
# fix for https://github.com/rails/rails/issues/40725
|
|
119
|
-
# TODO only apply to Active Record 6.0
|
|
120
|
-
if respond_to?(:inspection_filter, true) && v != "nil"
|
|
121
|
-
v = inspection_filter.filter_param(k, v)
|
|
122
|
-
end
|
|
123
117
|
else
|
|
124
118
|
next
|
|
125
119
|
end
|
|
@@ -148,7 +142,40 @@ module Lockbox
|
|
|
148
142
|
end
|
|
149
143
|
end
|
|
150
144
|
end
|
|
151
|
-
|
|
145
|
+
|
|
146
|
+
# remove attributes that do not have a ciphertext attribute
|
|
147
|
+
attributes = super
|
|
148
|
+
self.class.lockbox_attributes.each do |k, lockbox_attribute|
|
|
149
|
+
if !attributes.include?(lockbox_attribute[:encrypted_attribute].to_s)
|
|
150
|
+
attributes.delete(k.to_s)
|
|
151
|
+
attributes.delete(lockbox_attribute[:attribute])
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
attributes
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# remove attribute names that do not have a ciphertext attribute
|
|
158
|
+
def attribute_names
|
|
159
|
+
# hash preserves key order
|
|
160
|
+
names_set = super.to_h { |v| [v, true] }
|
|
161
|
+
self.class.lockbox_attributes.each do |k, lockbox_attribute|
|
|
162
|
+
if !names_set.include?(lockbox_attribute[:encrypted_attribute].to_s)
|
|
163
|
+
names_set.delete(k.to_s)
|
|
164
|
+
names_set.delete(lockbox_attribute[:attribute])
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
names_set.keys
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# check the ciphertext attribute for encrypted attributes
|
|
171
|
+
def has_attribute?(attr_name)
|
|
172
|
+
attr_name = attr_name.to_s
|
|
173
|
+
_, lockbox_attribute = self.class.lockbox_attributes.find { |_, la| la[:attribute] == attr_name }
|
|
174
|
+
if lockbox_attribute
|
|
175
|
+
super(lockbox_attribute[:encrypted_attribute])
|
|
176
|
+
else
|
|
177
|
+
super
|
|
178
|
+
end
|
|
152
179
|
end
|
|
153
180
|
|
|
154
181
|
# needed for in-place modifications
|
|
@@ -232,71 +259,69 @@ module Lockbox
|
|
|
232
259
|
result
|
|
233
260
|
end
|
|
234
261
|
|
|
235
|
-
if ActiveRecord::VERSION::
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
super(lockbox_map_record_attributes(attributes), **options)
|
|
239
|
-
end
|
|
240
|
-
|
|
241
|
-
def self.insert!(attributes, **options)
|
|
242
|
-
super(lockbox_map_record_attributes(attributes), **options)
|
|
243
|
-
end
|
|
244
|
-
|
|
245
|
-
def self.upsert(attributes, **options)
|
|
246
|
-
super(lockbox_map_record_attributes(attributes, check_readonly: true), **options)
|
|
247
|
-
end
|
|
262
|
+
if ActiveRecord::VERSION::STRING.to_f >= 7.2
|
|
263
|
+
def self.insert(attributes, **options)
|
|
264
|
+
super(lockbox_map_record_attributes(attributes), **options)
|
|
248
265
|
end
|
|
249
266
|
|
|
250
|
-
def self.
|
|
251
|
-
super(
|
|
267
|
+
def self.insert!(attributes, **options)
|
|
268
|
+
super(lockbox_map_record_attributes(attributes), **options)
|
|
252
269
|
end
|
|
253
270
|
|
|
254
|
-
def self.
|
|
255
|
-
super(
|
|
271
|
+
def self.upsert(attributes, **options)
|
|
272
|
+
super(lockbox_map_record_attributes(attributes, check_readonly: true), **options)
|
|
256
273
|
end
|
|
274
|
+
end
|
|
257
275
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
276
|
+
def self.insert_all(attributes, **options)
|
|
277
|
+
super(lockbox_map_attributes(attributes), **options)
|
|
278
|
+
end
|
|
261
279
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
return records unless records.is_a?(Array)
|
|
280
|
+
def self.insert_all!(attributes, **options)
|
|
281
|
+
super(lockbox_map_attributes(attributes), **options)
|
|
282
|
+
end
|
|
266
283
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
end
|
|
284
|
+
def self.upsert_all(attributes, **options)
|
|
285
|
+
super(lockbox_map_attributes(attributes, check_readonly: true), **options)
|
|
286
|
+
end
|
|
271
287
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
288
|
+
# private
|
|
289
|
+
# does not try to handle :returning option for simplicity
|
|
290
|
+
def self.lockbox_map_attributes(records, check_readonly: false)
|
|
291
|
+
return records unless records.is_a?(Array)
|
|
275
292
|
|
|
276
|
-
|
|
277
|
-
attributes
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
end
|
|
293
|
+
records.map do |attributes|
|
|
294
|
+
lockbox_map_record_attributes(attributes, check_readonly: false)
|
|
295
|
+
end
|
|
296
|
+
end
|
|
281
297
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
298
|
+
# private
|
|
299
|
+
def self.lockbox_map_record_attributes(attributes, check_readonly: false)
|
|
300
|
+
return attributes unless attributes.is_a?(Hash)
|
|
301
|
+
|
|
302
|
+
# transform keys like Active Record
|
|
303
|
+
attributes = attributes.transform_keys do |key|
|
|
304
|
+
n = key.to_s
|
|
305
|
+
attribute_aliases[n] || n
|
|
306
|
+
end
|
|
290
307
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
308
|
+
lockbox_attributes = self.lockbox_attributes.slice(*attributes.keys.map(&:to_sym))
|
|
309
|
+
lockbox_attributes.each do |key, lockbox_attribute|
|
|
310
|
+
attribute = key.to_s
|
|
311
|
+
# check read only
|
|
312
|
+
# users should mark both plaintext and ciphertext columns
|
|
313
|
+
if check_readonly && readonly_attributes.include?(attribute) && !readonly_attributes.include?(lockbox_attribute[:encrypted_attribute].to_s)
|
|
314
|
+
warn "[lockbox] WARNING: Mark attribute as readonly: #{lockbox_attribute[:encrypted_attribute]}"
|
|
296
315
|
end
|
|
297
316
|
|
|
298
|
-
attributes
|
|
317
|
+
message = attributes[attribute]
|
|
318
|
+
attributes.delete(attribute) unless lockbox_attribute[:migrating]
|
|
319
|
+
encrypted_attribute = lockbox_attribute[:encrypted_attribute]
|
|
320
|
+
ciphertext = send("generate_#{encrypted_attribute}", message)
|
|
321
|
+
attributes[encrypted_attribute] = ciphertext
|
|
299
322
|
end
|
|
323
|
+
|
|
324
|
+
attributes
|
|
300
325
|
end
|
|
301
326
|
else
|
|
302
327
|
def reload
|
|
@@ -327,13 +352,8 @@ module Lockbox
|
|
|
327
352
|
elsif attributes_to_define_after_schema_loads.key?(name.to_s)
|
|
328
353
|
opt = attributes_to_define_after_schema_loads[name.to_s][1]
|
|
329
354
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
# not ideal, since NO_DEFAULT_PROVIDED is private
|
|
333
|
-
opt != ActiveRecord::Attributes::ClassMethods.const_get(:NO_DEFAULT_PROVIDED)
|
|
334
|
-
else
|
|
335
|
-
opt.is_a?(Hash) && opt.key?(:default)
|
|
336
|
-
end
|
|
355
|
+
# not ideal, since NO_DEFAULT_PROVIDED is private
|
|
356
|
+
has_default = opt != ActiveRecord::Attributes::ClassMethods.const_get(:NO_DEFAULT_PROVIDED)
|
|
337
357
|
|
|
338
358
|
if has_default
|
|
339
359
|
warn "[lockbox] WARNING: attributes with `:default` option are not supported. Use `after_initialize` instead."
|
|
@@ -357,24 +377,13 @@ module Lockbox
|
|
|
357
377
|
|
|
358
378
|
attribute name, attribute_type
|
|
359
379
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
serialize name, type: Array, coder: default_column_serializer || YAML
|
|
368
|
-
end
|
|
369
|
-
else
|
|
370
|
-
case options[:type]
|
|
371
|
-
when :json
|
|
372
|
-
serialize name, JSON
|
|
373
|
-
when :hash
|
|
374
|
-
serialize name, Hash
|
|
375
|
-
when :array
|
|
376
|
-
serialize name, Array
|
|
377
|
-
end
|
|
380
|
+
case options[:type]
|
|
381
|
+
when :json
|
|
382
|
+
serialize name, coder: JSON
|
|
383
|
+
when :hash
|
|
384
|
+
serialize name, type: Hash, coder: default_column_serializer || YAML
|
|
385
|
+
when :array
|
|
386
|
+
serialize name, type: Array, coder: default_column_serializer || YAML
|
|
378
387
|
end
|
|
379
388
|
elsif ActiveRecord::VERSION::STRING.to_f >= 7.2
|
|
380
389
|
decorate_attributes([name]) do |attr_name, cast_type|
|
|
@@ -408,21 +417,14 @@ module Lockbox
|
|
|
408
417
|
else
|
|
409
418
|
attribute name, :string
|
|
410
419
|
end
|
|
411
|
-
|
|
420
|
+
elsif attributes_to_define_after_schema_loads[name.to_s].first.is_a?(Proc)
|
|
412
421
|
# hack for Active Record 6.1+ to set string type after serialize
|
|
413
422
|
# otherwise, type gets set to ActiveModel::Type::Value
|
|
414
423
|
# which always returns false for changed_in_place?
|
|
415
424
|
# earlier versions of Active Record take the previous code path
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
attribute name, ActiveRecord::Type::Serialized.new(ActiveRecord::Type::String.new, attribute_type.coder)
|
|
420
|
-
end
|
|
421
|
-
elsif ActiveRecord::VERSION::STRING.to_f >= 6.1 && attributes_to_define_after_schema_loads[name.to_s].first.is_a?(Proc)
|
|
422
|
-
attribute_type = attributes_to_define_after_schema_loads[name.to_s].first.call
|
|
423
|
-
if attribute_type.is_a?(ActiveRecord::Type::Serialized) && attribute_type.subtype.nil?
|
|
424
|
-
attribute name, ActiveRecord::Type::Serialized.new(ActiveRecord::Type::String.new, attribute_type.coder)
|
|
425
|
-
end
|
|
425
|
+
attribute_type = attributes_to_define_after_schema_loads[name.to_s].first.call(nil)
|
|
426
|
+
if attribute_type.is_a?(ActiveRecord::Type::Serialized) && attribute_type.subtype.nil?
|
|
427
|
+
attribute name, ActiveRecord::Type::Serialized.new(ActiveRecord::Type::String.new, attribute_type.coder)
|
|
426
428
|
end
|
|
427
429
|
end
|
|
428
430
|
|
|
@@ -581,7 +583,6 @@ module Lockbox
|
|
|
581
583
|
case options[:type]
|
|
582
584
|
when :boolean
|
|
583
585
|
message = ActiveRecord::Type::Boolean.new.serialize(message)
|
|
584
|
-
message = nil if message == "" # for Active Record < 5.2
|
|
585
586
|
message = message ? "t" : "f" unless message.nil?
|
|
586
587
|
when :date
|
|
587
588
|
message = ActiveRecord::Type::Date.new.serialize(message)
|
|
@@ -589,7 +590,6 @@ module Lockbox
|
|
|
589
590
|
message = message.strftime("%Y-%m-%d") unless message.nil?
|
|
590
591
|
when :datetime
|
|
591
592
|
message = ActiveRecord::Type::DateTime.new.serialize(message)
|
|
592
|
-
message = nil unless message.respond_to?(:iso8601) # for Active Record < 5.2
|
|
593
593
|
message = message.iso8601(9) unless message.nil?
|
|
594
594
|
when :time
|
|
595
595
|
message = ActiveRecord::Type::Time.new.serialize(message)
|
|
@@ -606,14 +606,7 @@ module Lockbox
|
|
|
606
606
|
# double precision, big endian
|
|
607
607
|
message = [message].pack("G") unless message.nil?
|
|
608
608
|
when :decimal
|
|
609
|
-
message =
|
|
610
|
-
if ActiveRecord::VERSION::MAJOR >= 6
|
|
611
|
-
ActiveRecord::Type::Decimal.new.serialize(message)
|
|
612
|
-
else
|
|
613
|
-
# issue with serialize in Active Record < 6
|
|
614
|
-
# https://github.com/rails/rails/commit/a741208f80dd33420a56486bd9ed2b0b9862234a
|
|
615
|
-
ActiveRecord::Type::Decimal.new.cast(message)
|
|
616
|
-
end
|
|
609
|
+
message = ActiveRecord::Type::Decimal.new.serialize(message)
|
|
617
610
|
# Postgres stores 4 decimal digits in 2 bytes
|
|
618
611
|
# plus 3 to 8 bytes of overhead
|
|
619
612
|
# but use string for simplicity
|
|
@@ -716,12 +709,6 @@ module Lockbox
|
|
|
716
709
|
end
|
|
717
710
|
end
|
|
718
711
|
|
|
719
|
-
def lockbox_encrypts(*attributes, **options)
|
|
720
|
-
deprecator = ActiveSupport::VERSION::STRING.to_f >= 7.2 ? ActiveSupport.deprecator : ActiveSupport::Deprecation
|
|
721
|
-
deprecator.warn("`#{__callee__}` is deprecated in favor of `has_encrypted`")
|
|
722
|
-
has_encrypted(*attributes, **options)
|
|
723
|
-
end
|
|
724
|
-
|
|
725
712
|
module Attached
|
|
726
713
|
def encrypts_attached(*attributes, **options)
|
|
727
714
|
attributes.each do |name|
|
data/lib/lockbox/railtie.rb
CHANGED
|
@@ -12,32 +12,15 @@ module Lockbox
|
|
|
12
12
|
require "lockbox/active_storage_extensions"
|
|
13
13
|
|
|
14
14
|
ActiveStorage::Attached.prepend(Lockbox::ActiveStorageExtensions::Attached)
|
|
15
|
-
|
|
16
|
-
ActiveStorage::Attached::Changes::CreateOne.prepend(Lockbox::ActiveStorageExtensions::CreateOne)
|
|
17
|
-
end
|
|
15
|
+
ActiveStorage::Attached::Changes::CreateOne.prepend(Lockbox::ActiveStorageExtensions::CreateOne)
|
|
18
16
|
ActiveStorage::Attached::One.prepend(Lockbox::ActiveStorageExtensions::AttachedOne)
|
|
19
17
|
ActiveStorage::Attached::Many.prepend(Lockbox::ActiveStorageExtensions::AttachedMany)
|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
ActiveSupport.on_load(:active_storage_blob) do
|
|
27
|
-
prepend Lockbox::ActiveStorageExtensions::Blob
|
|
28
|
-
end
|
|
29
|
-
elsif ActiveStorage::VERSION::MAJOR >= 6
|
|
30
|
-
ActiveSupport.on_load(:active_storage_attachment) do
|
|
31
|
-
include Lockbox::ActiveStorageExtensions::Attachment
|
|
32
|
-
end
|
|
33
|
-
ActiveSupport.on_load(:active_storage_blob) do
|
|
34
|
-
prepend Lockbox::ActiveStorageExtensions::Blob
|
|
35
|
-
end
|
|
36
|
-
else
|
|
37
|
-
app.config.to_prepare do
|
|
38
|
-
ActiveStorage::Attachment.include(Lockbox::ActiveStorageExtensions::Attachment)
|
|
39
|
-
ActiveStorage::Blob.prepend(Lockbox::ActiveStorageExtensions::Blob)
|
|
40
|
-
end
|
|
19
|
+
ActiveSupport.on_load(:active_storage_attachment) do
|
|
20
|
+
prepend Lockbox::ActiveStorageExtensions::Attachment
|
|
21
|
+
end
|
|
22
|
+
ActiveSupport.on_load(:active_storage_blob) do
|
|
23
|
+
prepend Lockbox::ActiveStorageExtensions::Blob
|
|
41
24
|
end
|
|
42
25
|
end
|
|
43
26
|
end
|
data/lib/lockbox/version.rb
CHANGED
data/lib/lockbox.rb
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
# stdlib
|
|
2
|
-
require "base64"
|
|
3
2
|
require "openssl"
|
|
4
3
|
require "securerandom"
|
|
5
4
|
require "stringio"
|
|
@@ -99,8 +98,12 @@ end
|
|
|
99
98
|
if defined?(ActiveSupport.on_load)
|
|
100
99
|
ActiveSupport.on_load(:active_record) do
|
|
101
100
|
ar_version = ActiveRecord::VERSION::STRING.to_f
|
|
102
|
-
if ar_version <
|
|
103
|
-
if ar_version >=
|
|
101
|
+
if ar_version < 7.1
|
|
102
|
+
if ar_version >= 7.0
|
|
103
|
+
raise Lockbox::Error, "Active Record #{ActiveRecord::VERSION::STRING} requires Lockbox < 2.1"
|
|
104
|
+
elsif ar_version >= 5.2
|
|
105
|
+
raise Lockbox::Error, "Active Record #{ActiveRecord::VERSION::STRING} requires Lockbox < 2"
|
|
106
|
+
elsif ar_version >= 5
|
|
104
107
|
raise Lockbox::Error, "Active Record #{ActiveRecord::VERSION::STRING} requires Lockbox < 0.7"
|
|
105
108
|
else
|
|
106
109
|
raise Lockbox::Error, "Active Record #{ActiveRecord::VERSION::STRING} not supported"
|
|
@@ -109,12 +112,19 @@ if defined?(ActiveSupport.on_load)
|
|
|
109
112
|
|
|
110
113
|
extend Lockbox::Model
|
|
111
114
|
extend Lockbox::Model::Attached
|
|
112
|
-
singleton_class.alias_method(:encrypts, :lockbox_encrypts) if ActiveRecord::VERSION::MAJOR < 7
|
|
113
115
|
ActiveRecord::Relation.prepend Lockbox::Calculations
|
|
114
116
|
end
|
|
115
117
|
|
|
116
118
|
ActiveSupport.on_load(:mongoid) do
|
|
119
|
+
mongoid_version = Mongoid::VERSION.to_i
|
|
120
|
+
if mongoid_version < 8
|
|
121
|
+
if mongoid_version >= 6
|
|
122
|
+
raise Lockbox::Error, "Mongoid #{Mongoid::VERSION} requires Lockbox < 2"
|
|
123
|
+
else
|
|
124
|
+
raise Lockbox::Error, "Mongoid #{Mongoid::VERSION} not supported"
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
117
128
|
Mongoid::Document::ClassMethods.include(Lockbox::Model)
|
|
118
|
-
Mongoid::Document::ClassMethods.alias_method(:encrypts, :lockbox_encrypts)
|
|
119
129
|
end
|
|
120
130
|
end
|
metadata
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lockbox
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 2.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrew Kane
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies: []
|
|
13
|
-
description:
|
|
14
12
|
email: andrew@ankane.org
|
|
15
13
|
executables: []
|
|
16
14
|
extensions: []
|
|
@@ -43,7 +41,6 @@ homepage: https://github.com/ankane/lockbox
|
|
|
43
41
|
licenses:
|
|
44
42
|
- MIT
|
|
45
43
|
metadata: {}
|
|
46
|
-
post_install_message:
|
|
47
44
|
rdoc_options: []
|
|
48
45
|
require_paths:
|
|
49
46
|
- lib
|
|
@@ -51,15 +48,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
51
48
|
requirements:
|
|
52
49
|
- - ">="
|
|
53
50
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: '2
|
|
51
|
+
version: '3.2'
|
|
55
52
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
56
53
|
requirements:
|
|
57
54
|
- - ">="
|
|
58
55
|
- !ruby/object:Gem::Version
|
|
59
56
|
version: '0'
|
|
60
57
|
requirements: []
|
|
61
|
-
rubygems_version: 3.
|
|
62
|
-
signing_key:
|
|
58
|
+
rubygems_version: 3.6.9
|
|
63
59
|
specification_version: 4
|
|
64
60
|
summary: Modern encryption for Ruby and Rails
|
|
65
61
|
test_files: []
|