crypt_keeper 0.8.0 → 0.9.0.pre

Sign up to get free protection for your applications and to get access to all the features.
@@ -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