lockbox 0.3.2 → 0.3.3

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