lockbox 0.3.2 → 0.3.3

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: 17af286829bd927b3f3dcf156672a4dbced52dcc083d8920a24230cb89f5104a
4
- data.tar.gz: 30ec2e0be697c31933b88930c02a43e5544708fe8f847b813a42bd05cd9354dc
3
+ metadata.gz: 8198d9c7bf3be294c7efc7c7d03abbe64ccf8d487a0a81ad01151b225dbfde92
4
+ data.tar.gz: dd51f9c1dc06a4cc657fb8b25a05f4a2960a2bce6883fd97dd41d1ac7e426e91
5
5
  SHA512:
6
- metadata.gz: a2b0cfd5fff48b4907a29d1d67499e3bdb81d29f81c9843fae1f447ecf5dfad5d61bb383877324e6cb35f3aac9987f867f12a4106108fe5bc638f93b6fffe39a
7
- data.tar.gz: be8abd51a5f0b8cea3d6cbd18d75bf1248333b9a575a5088b3d36c428fa1103f1645b5ca6b94bbf8dfcf2e9b1d3c31a14bd6f06fe9b9da8c26022bbb78e428c7
6
+ metadata.gz: fe9c1274693558f3e184b7881f027c0e1ce895aeacd2c222451ea87a61ed7ff1064ed324a2e11a3bf35f60fa3647698ded89574a8c378ef42c0b538fe6606e1c
7
+ data.tar.gz: 65ea6fb170250edf535692e085700430e4fe25ebca07e63d17de7e2f99afcb0d3089787a4b90c193b3314ae9bcde9540454eae3685a242da1e166c36d2982b19
@@ -1,3 +1,8 @@
1
+ ## 0.3.3 (2020-02-16)
2
+
3
+ - Improved performance of `rotate` for attributes with blind indexes
4
+ - Added warning when decrypting previous value fails
5
+
1
6
  ## 0.3.2 (2020-02-14)
2
7
 
3
8
  - Added `encode` option to `Lockbox::Encryptor`
data/README.md CHANGED
@@ -176,7 +176,7 @@ end
176
176
 
177
177
  Finally, drop the unencrypted column.
178
178
 
179
- If adding blind indexes, Lockbox can migrate them at the same time.
179
+ If adding blind indexes, mark them as `migrating` during this process as well.
180
180
 
181
181
  ```ruby
182
182
  class User < ApplicationRecord
@@ -13,19 +13,20 @@ module Lockbox
13
13
  def rotate(attributes:)
14
14
  fields = {}
15
15
  attributes.each do |a|
16
- # use key instad of v[:attribute] to make it more intuitive when migrating: true
16
+ # use key instead of v[:attribute] to make it more intuitive when migrating: true
17
17
  field = model.lockbox_attributes[a]
18
18
  raise ArgumentError, "Bad attribute: #{a}" unless field
19
19
  fields[a] = field
20
20
  end
21
21
 
22
- perform(fields: fields)
22
+ perform(fields: fields, rotate: true)
23
23
  end
24
24
 
25
25
  # TODO add attributes option
26
26
  def migrate(restart:)
27
27
  fields = model.lockbox_attributes.select { |k, v| v[:migrating] }
28
28
 
29
+ # need blind indexes for building relation
29
30
  blind_indexes = model.respond_to?(:blind_indexes) ? model.blind_indexes.select { |k, v| v[:migrating] } : {}
30
31
 
31
32
  perform(fields: fields, blind_indexes: blind_indexes, restart: restart)
@@ -33,11 +34,14 @@ module Lockbox
33
34
 
34
35
  private
35
36
 
36
- def perform(fields:, blind_indexes: [], restart: true)
37
+ def perform(fields:, blind_indexes: [], restart: true, rotate: false)
37
38
  relation = @relation
38
39
 
39
- # remove true condition in 0.4.0
40
- if true || (defined?(ActiveRecord::Base) && base_relation.is_a?(ActiveRecord::Base))
40
+ # unscope if passed a model
41
+ unless ar_relation?(relation) || mongoid_relation?(relation)
42
+ relation = relation.unscoped
43
+ else
44
+ # TODO remove in 0.4.0
41
45
  relation = relation.unscoped
42
46
  end
43
47
 
@@ -48,7 +52,7 @@ module Lockbox
48
52
  attributes = fields.map { |_, v| v[:encrypted_attribute] }
49
53
  attributes += blind_indexes.map { |_, v| v[:bidx_attribute] }
50
54
 
51
- if defined?(ActiveRecord::Relation) && relation.is_a?(ActiveRecord::Relation)
55
+ if ar_relation?(relation)
52
56
  base_relation = relation.unscoped
53
57
  or_relation = relation.unscoped
54
58
 
@@ -68,7 +72,7 @@ module Lockbox
68
72
  end
69
73
 
70
74
  each_batch(relation) do |records|
71
- migrate_records(records, fields: fields, blind_indexes: blind_indexes, restart: restart)
75
+ migrate_records(records, fields: fields, blind_indexes: blind_indexes, restart: restart, rotate: rotate)
72
76
  end
73
77
  end
74
78
 
@@ -92,27 +96,59 @@ module Lockbox
92
96
  end
93
97
  end
94
98
 
95
- def migrate_records(records, fields:, blind_indexes:, restart:)
99
+ def migrate_records(records, fields:, blind_indexes:, restart:, rotate:)
96
100
  # do computation outside of transaction
97
101
  # especially expensive blind index computation
98
- records.each do |record|
99
- fields.each do |k, v|
100
- record.send("#{v[:attribute]}=", record.send(k)) if restart || !record.send(v[:encrypted_attribute])
102
+ if rotate
103
+ records.each do |record|
104
+ fields.each do |k, v|
105
+ # update encrypted attribute directly to skip blind index computation
106
+ record.send("lockbox_direct_#{k}=", record.send(k))
107
+ end
101
108
  end
102
- blind_indexes.each do |k, v|
103
- record.send("compute_#{k}_bidx") if restart || !record.send(v[:bidx_attribute])
109
+ else
110
+ records.each do |record|
111
+ if restart
112
+ fields.each do |k, v|
113
+ record.send("#{v[:encrypted_attribute]}=", nil)
114
+ end
115
+
116
+ blind_indexes.each do |k, v|
117
+ record.send("#{v[:bidx_attribute]}=", nil)
118
+ end
119
+ end
120
+
121
+ fields.each do |k, v|
122
+ record.send("#{v[:attribute]}=", record.send(k)) unless record.send(v[:encrypted_attribute])
123
+ end
124
+
125
+ # with Blind Index 2.0, bidx_attribute should be already set for each record
126
+ blind_indexes.each do |k, v|
127
+ record.send("compute_#{k}_bidx") unless record.send(v[:bidx_attribute])
128
+ end
104
129
  end
105
130
  end
106
131
 
132
+ # don't need to save records that went from nil => nil
107
133
  records.select! { |r| r.changed? }
108
134
 
109
- with_transaction do
110
- records.each do |record|
111
- record.save!(validate: false)
135
+ if records.any?
136
+ with_transaction do
137
+ records.each do |record|
138
+ record.save!(validate: false)
139
+ end
112
140
  end
113
141
  end
114
142
  end
115
143
 
144
+ def ar_relation?(relation)
145
+ defined?(ActiveRecord::Relation) && relation.is_a?(ActiveRecord::Relation)
146
+ end
147
+
148
+ def mongoid_relation?(relation)
149
+ defined?(Mongoid::Criteria) && relation.is_a?(Mongoid::Criteria)
150
+ end
151
+
116
152
  def with_transaction
117
153
  if @transaction
118
154
  @relation.transaction do
@@ -191,22 +191,28 @@ module Lockbox
191
191
  end
192
192
 
193
193
  define_method("#{name}=") do |message|
194
- original_message = message
195
-
196
194
  # decrypt first for dirty tracking
197
195
  # don't raise error if can't decrypt previous
198
196
  begin
199
197
  send(name)
200
198
  rescue Lockbox::DecryptionError
199
+ # this is expected for hybrid cryptography
200
+ warn "[lockbox] Decrypting previous value failed" unless options[:algorithm] == "hybrid"
201
201
  nil
202
202
  end
203
203
 
204
- # set ciphertext
204
+ send("lockbox_direct_#{name}=", message)
205
+
206
+ super(message)
207
+ end
208
+
209
+ # separate method for setting directly
210
+ # used to skip blind indexes for key rotation
211
+ define_method("lockbox_direct_#{name}=") do |message|
205
212
  ciphertext = self.class.send(encrypt_method_name, message, context: self)
206
213
  send("#{encrypted_attribute}=", ciphertext)
207
-
208
- super(original_message)
209
214
  end
215
+ private :"lockbox_direct_#{name}="
210
216
 
211
217
  define_method(name) do
212
218
  message = super()
@@ -1,3 +1,3 @@
1
1
  module Lockbox
2
- VERSION = "0.3.2"
2
+ VERSION = "0.3.3"
3
3
  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.3.2
4
+ version: 0.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-02-14 00:00:00.000000000 Z
11
+ date: 2020-02-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler