crypt_keeper 0.8.0 → 0.9.0.pre

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.
@@ -1,5 +1,5 @@
1
1
  PATH
2
- remote: /Users/justin/work/Personal/crypt_keeper
2
+ remote: /Users/justin/work/jmazzi/crypt_keeper
3
3
  specs:
4
4
  crypt_keeper (0.8.0)
5
5
  activerecord (>= 3.0)
@@ -1,5 +1,5 @@
1
1
  PATH
2
- remote: /Users/justin/work/Personal/crypt_keeper
2
+ remote: /Users/justin/work/jmazzi/crypt_keeper
3
3
  specs:
4
4
  crypt_keeper (0.8.0)
5
5
  activerecord (>= 3.0)
@@ -1,5 +1,5 @@
1
1
  PATH
2
- remote: /Users/justin/work/Personal/crypt_keeper
2
+ remote: /Users/justin/work/jmazzi/crypt_keeper
3
3
  specs:
4
4
  crypt_keeper (0.8.0)
5
5
  activerecord (>= 3.0)
@@ -17,11 +17,31 @@ module CryptKeeper
17
17
 
18
18
  private
19
19
 
20
+ # Private: A hash of encrypted attributes with their encrypted values
21
+ #
22
+ # Returns a Hash
23
+ def crypt_keeper_dirty_tracking
24
+ @crypt_keeper_dirty_tracking ||= HashWithIndifferentAccess.new
25
+ end
26
+
27
+ # Private: Determine if the field's plaintext value changed. It compares
28
+ # it to the original value that came from the DB before decryption
29
+ #
30
+ # Returns boolean
31
+ def plaintext_changed?(field)
32
+ new_record? || self[field] != self.class.decrypt(crypt_keeper_dirty_tracking[field])
33
+ end
34
+
20
35
  # Private: Encrypt each crypt_keeper_fields
21
36
  def encrypt_callback
22
37
  crypt_keeper_fields.each do |field|
23
38
  if !self[field].nil?
24
- self[field] = self.class.encrypt read_attribute(field)
39
+ if plaintext_changed?(field)
40
+ self[field] = self.class.encrypt read_attribute(field)
41
+ else
42
+ self[field] = crypt_keeper_dirty_tracking[field]
43
+ clear_field_changes! field
44
+ end
25
45
  end
26
46
  end
27
47
  end
@@ -30,8 +50,11 @@ module CryptKeeper
30
50
  def decrypt_callback
31
51
  crypt_keeper_fields.each do |field|
32
52
  if !self[field].nil?
53
+ crypt_keeper_dirty_tracking[field] = read_attribute(field)
33
54
  self[field] = self.class.decrypt read_attribute(field)
34
55
  end
56
+
57
+ clear_field_changes! field
35
58
  end
36
59
  end
37
60
 
@@ -42,6 +65,15 @@ module CryptKeeper
42
65
  end
43
66
  end
44
67
 
68
+ # Private: Removes changes from `#previous_changes` and
69
+ # `#changed_attributes` so the model isn't considered dirty.
70
+ #
71
+ # field - The field to clear
72
+ def clear_field_changes!(field)
73
+ previous_changes.delete(field.to_s)
74
+ changed_attributes.delete(field.to_s)
75
+ end
76
+
45
77
  module ClassMethods
46
78
  # Public: Setup fields for encryption
47
79
  #
@@ -33,9 +33,7 @@ module CryptKeeper
33
33
  def encrypt(value)
34
34
  aes.encrypt
35
35
  aes.key = key
36
- iv = rand.to_s
37
- aes.iv = iv
38
- Base64::encode64("#{iv}#{SEPARATOR}#{aes.update(value.to_s) + aes.final}")
36
+ Base64::encode64("#{aes.random_iv}#{SEPARATOR}#{aes.update(value.to_s) + aes.final}")
39
37
  end
40
38
 
41
39
  # Public: Decrypt a string
@@ -1,3 +1,3 @@
1
1
  module CryptKeeper
2
- VERSION = "0.8.0"
2
+ VERSION = "0.9.0.pre"
3
3
  end
@@ -118,6 +118,34 @@ module CryptKeeper
118
118
  SensitiveData.crypt_keeper_options.should include(passphrase: 'tool')
119
119
  end
120
120
  end
121
+
122
+ describe "Dirty records" do
123
+ before do
124
+ SensitiveData.crypt_keeper :storage, passphrase: 'tool', encryptor: :postgres_pgp
125
+ end
126
+
127
+ let(:record) do
128
+ SensitiveData.create storage: 'test'
129
+ end
130
+
131
+ specify { record.should_not be_changed }
132
+
133
+ it "unchanged plaintext does not trigger a save" do
134
+ queries = []
135
+
136
+ subscriber = ActiveSupport::Notifications.subscribe('sql.active_record') do |name, started, finished, id, payload|
137
+ queries << payload[:sql]
138
+ end
139
+
140
+ SensitiveData.find(record.id).save
141
+ ActiveSupport::Notifications.unsubscribe subscriber
142
+
143
+ updates = queries.select { |query| query.match(/^UPDATE /) }
144
+
145
+ queries.should_not be_empty
146
+ updates.should be_empty, "Received #{updates}"
147
+ end
148
+ end
121
149
  end
122
150
  end
123
151
  end
@@ -22,7 +22,7 @@ module CryptKeeper
22
22
  end
23
23
 
24
24
  def decrypt(data)
25
- data.sub(/^#{@passphrase}/, '').reverse
25
+ data.to_s.sub(/^#{@passphrase}/, '').reverse
26
26
  end
27
27
  end
28
28
  end
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: crypt_keeper
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
5
- prerelease:
4
+ version: 0.9.0.pre
5
+ prerelease: 6
6
6
  platform: ruby
7
7
  authors:
8
8
  - Justin Mazzi
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-24 00:00:00.000000000 Z
12
+ date: 2013-03-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
@@ -243,16 +243,13 @@ required_ruby_version: !ruby/object:Gem::Requirement
243
243
  version: '0'
244
244
  segments:
245
245
  - 0
246
- hash: 838883882659964076
246
+ hash: 3463799705679216715
247
247
  required_rubygems_version: !ruby/object:Gem::Requirement
248
248
  none: false
249
249
  requirements:
250
- - - ! '>='
250
+ - - ! '>'
251
251
  - !ruby/object:Gem::Version
252
- version: '0'
253
- segments:
254
- - 0
255
- hash: 838883882659964076
252
+ version: 1.3.1
256
253
  requirements: []
257
254
  rubyforge_project:
258
255
  rubygems_version: 1.8.24