lockbox 0.6.2 → 0.6.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4ba37bc916c02e18555640f29e47483898a96e04b75639b49ce9a0003fbaa443
4
- data.tar.gz: 750a53ca3201e51b6dc0221305d4c021d64716a163a2e0cfc98c11ec8d1b6af8
3
+ metadata.gz: 0cc5589dbbfae34908ffdd634798549fa161c7dc79b528f23b8e097a044f3d9d
4
+ data.tar.gz: 3eb23c1dfb284abb335e2addfb20fee77b31373b561ab6c0f9afaf28abf8aeb8
5
5
  SHA512:
6
- metadata.gz: b4e23752c311bf6b161e6817ab204ba0b5936594db5c2509c3f5ef6e354c169097a1c799d7b5147daa0d68982f072d1d22363019c6881566403517f97a5f2c45
7
- data.tar.gz: 51ab913facdc34aea3e3ef263dab2fa5c3852aa052de80804ec29019c2517df9244eefb8c15e6f2c1ca0059bfd4f9cc020ce00fff543874f6461faeec1e78443
6
+ metadata.gz: b0bd8a7ee500cbba73daa2ef0c9c6af07695b53799735f865d5b0e9e907e8ef86904983d5f8cfdff098b554d4cd9d4351133778079bde93a7440bbacf03d0545
7
+ data.tar.gz: ea83a9ecb7734e907a03be89e628fc103815b0cc5acc2aec0c21dc0f2d6b7e746f299a489d4a11b14f413a55c4fb06b45d6e23302c853c8b216ad2bca63218d6
data/CHANGELOG.md CHANGED
@@ -1,4 +1,32 @@
1
- ## 0.6.2 (2020-02-08)
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
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2018-2021 Andrew Kane
3
+ Copyright (c) 2018-2022 Andrew Kane
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
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 'lockbox'
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.0]
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.0]
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
- #### Migrating Existing Files [experimental]
341
+ Use `filename` to specify a filename or `disposition: "inline"` to show inline.
340
342
 
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.
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.0]
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: [{key: previous_key}]
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: [{key: previous_key}])
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: [{key: previous_key}]
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: [{key: previous_key}]
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 'rbnacl'
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.0]
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.0]
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.0]
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.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.8"
3
3
  end
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 <= 5.0
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
- 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.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: 2021-02-08 00:00:00.000000000 Z
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.2.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