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