attr_encrypted 3.1.0 → 4.1.0

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
- SHA1:
3
- metadata.gz: f602391484e3437daae62ddb60596f0b4cbc83e0
4
- data.tar.gz: 5e407d7e299a43380057a2f0fe816da3a6ad1fca
2
+ SHA256:
3
+ metadata.gz: 5a992ab4061d1d05c6de0891accee520006d545d727be49bd80c09ea4d55b44a
4
+ data.tar.gz: c6dcfb039445ce7128e0c610d5ca6a4efce02ed195fe754fbc947a99f7b8cef7
5
5
  SHA512:
6
- metadata.gz: 77b697d9e450d6baae65fb3ed339824b8bf730898296f172c2ae6b4e0bd7368add0da29c76596b9bbf71d298fccd038195f7ffb7d153ffaeaf2aabc7bfc60f9f
7
- data.tar.gz: 211f1c52e7f0505d55eef59f06bb139f47fb1ae0158b39a643b5c7c5251865f13b871ab7b3194d44cd2fc6a128e86a5c6b9c8669bab6d2870a7914ed5dacb6c3
6
+ metadata.gz: 62299d9d34f907ef99464ca41c5de51f9b05d440b2210ba377e05ac4fe02978fa58893cdb0ce002cd25216dace37c5f67b8a8c97ef76259066ee6fc57b93fd08
7
+ data.tar.gz: f081aaebe945a24d2aff5fea11e671b700b8bf5a6513d36252c96f38e5134448efb9660d0fb6843418d926dcb3821ae1e9c23d2f2551ff2a0b261a678a6bb8f5
@@ -0,0 +1,43 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+ pull_request:
8
+
9
+ jobs:
10
+ test:
11
+ name: Ruby ${{ matrix.ruby }} / ActiveRecord ${{ matrix.active_record }}
12
+ runs-on: ubuntu-latest
13
+ strategy:
14
+ fail-fast: false
15
+ matrix:
16
+ active_record:
17
+ - '6.0'
18
+ - '6.1'
19
+ - '7.0'
20
+ - '7.1'
21
+ ruby:
22
+ - '2.7'
23
+ - '3.0'
24
+ - '3.1'
25
+ - '3.2'
26
+ include:
27
+ - active_record: '5.1'
28
+ ruby: '2.7'
29
+ - active_record: '5.2'
30
+ ruby: '2.7'
31
+ - active_record: '7.1'
32
+ ruby: '3.3'
33
+ env:
34
+ ACTIVERECORD: ${{ matrix.active_record }}
35
+ steps:
36
+ - uses: actions/checkout@v3
37
+ - name: Set up Ruby
38
+ uses: ruby/setup-ruby@v1
39
+ with:
40
+ ruby-version: ${{ matrix.ruby }}
41
+ bundler-cache: true
42
+ - run: |
43
+ bundle exec rake test
data/CHANGELOG.md CHANGED
@@ -1,6 +1,19 @@
1
- # attr_encrypted #
1
+ # attr_encrypted
2
+
3
+ ## 4.1.0
4
+
5
+ * Changed: Dropped support for `datamapper` which has not had a release since October 2011. This is in an attempt to make
6
+ maintenance and testing easier moving forward.
7
+
8
+ ## 4.0.0
9
+
10
+ * Added: Support for Ruby >= 3.0.
11
+ * Added: Rails 7 support.
12
+ * Changed: Using `#encrypted_attributes` is no longer supported. Instead, use `#attr_encrypted_encrypted_attributes` to avoid
13
+ collision with Active Record 7 native encryption.
14
+
15
+ ## 3.1.0
2
16
 
3
- ## 3.1.0 ##
4
17
  * Added: Abitilty to encrypt empty values. (@tamird)
5
18
  * Added: MIT license
6
19
  * Added: MRI 2.5.x support (@saghaulor)
@@ -11,23 +24,28 @@
11
24
  * Fixed: Only check empty on strings, allows for encrypting non-string type objects
12
25
  * Fixed: Fixed how accessors for db columns are defined in the ActiveRecord adapter, preventing premature definition. (@nagachika)
13
26
 
14
- ## 3.0.3 ##
27
+ ## 3.0.3
28
+
15
29
  * Fixed: attr_was would decrypt the attribute upon every call. This is inefficient and introduces problems when the options change between decrypting an old value and encrypting a new value; for example, when rotating the encryption key. As such, the new approach caches the decrypted value of the old encrypted value such that the old options are no longer needed. (@johnny-lai) (@saghaulor)
16
30
 
17
- ## 3.0.2 ##
31
+ ## 3.0.2
32
+
18
33
  * Changed: Removed alias_method_chain for compatibility with Rails v5.x (@grosser)
19
34
  * Changed: Updated Travis build matrix to include Rails 5. (@saghaulor) (@connorshea)
20
35
  * Changed: Removed `.silence_stream` from tests as it has been removed from Rails 5. (@sblackstone)
21
36
 
22
- ## 3.0.1 ##
37
+ ## 3.0.1
38
+
23
39
  * Fixed: attr_was method no longer calls undefined methods. (@saghaulor)
24
40
 
25
- ## 3.0.0 ##
41
+ ## 3.0.0
42
+
26
43
  * Changed: Updated gemspec to use Encryptor v3.0.0. (@saghaulor)
27
44
  * Changed: Updated README with instructions related to moving from v2.0.0 to v3.0.0. (@saghaulor)
28
45
  * Fixed: ActiveModel::Dirty methods in the ActiveRecord adapter. (@saghaulor)
29
46
 
30
- ## 2.0.0 ##
47
+ ## 2.0.0
48
+
31
49
  * Added: Now using Encryptor v2.0.0 (@saghaulor)
32
50
  * Added: Options are copied to the instance. (@saghaulor)
33
51
  * Added: Operation option is set during encryption/decryption to allow options to be evaluated in the context of the current operation. (@saghaulor)
@@ -48,51 +66,62 @@
48
66
  * Removed: Support for Rails < 3.x (@saghaulor)
49
67
  * Removed: Unnecessary use of `alias_method` from ActiveRecord adapter. (@saghaulor)
50
68
 
51
- ## 1.4.0 ##
69
+ ## 1.4.0
70
+
52
71
  * Added: ActiveModel::Dirty#attribute_was (@saghaulor)
53
72
  * Added: ActiveModel::Dirty#attribute_changed? (@mwean)
54
73
 
55
- ## 1.3.5 ##
74
+ ## 1.3.5
75
+
56
76
  * Changed: Fixed gemspec to explicitly depend on Encryptor v1.3.0 (@saghaulor)
57
77
  * Fixed: Evaluate `:mode` option as a symbol or proc. (@cheynewallace)
58
78
 
59
- ## 1.3.4 ##
79
+ ## 1.3.4
80
+
60
81
  * Added: ActiveRecord::Base.reload support. (@rcook)
61
82
  * Fixed: ActiveRecord adapter no longer forces attribute hashes to be string-keyed. (@tamird)
62
83
  * Fixed: Mass assignment protection in ActiveRecord 4. (@tamird)
63
84
  * Changed: Now using rubygems over https. (@tamird)
64
85
  * Changed: Let ActiveRecord define attribute methods. (@saghaulor)
65
86
 
66
- ## 1.3.3 ##
87
+ ## 1.3.3
88
+
67
89
  * Added: Alias attr_encryptor and attr_encrpted. (@Billy Monk)
68
90
 
69
- ## 1.3.2 ##
91
+ ## 1.3.2
92
+
70
93
  * Fixed: Bug regarding strong parameters. (@S. Brent Faulkner)
71
94
  * Fixed: Bug regarding loading per instance IV and salt. (@S. Brent Faulkner)
72
95
  * Fixed: Bug regarding assigning nil. (@S. Brent Faulkner)
73
96
  * Added: Support for protected attributes. (@S. Brent Faulkner)
74
97
  * Added: Support for ActiveRecord 4. (@S. Brent Faulkner)
75
98
 
76
- ## 1.3.1 ##
99
+ ## 1.3.1
100
+
77
101
  * Added: Support for Rails 2.3.x and 3.1.x. (@S. Brent Faulkner)
78
102
 
79
- ## 1.3.0 ##
103
+ ## 1.3.0
104
+
80
105
  * Fixed: Serialization bug. (@Billy Monk)
81
106
  * Added: Support for :per_attribute_iv_and_salt mode. (@rcook)
82
107
  * Fixed: Added dependencies to gemspec. (@jmazzi)
83
108
 
84
- ## 1.2.1 ##
109
+ ## 1.2.1
110
+
85
111
  * Added: Force encoding when not marshaling. (@mosaicxm)
86
112
  * Fixed: Issue specifying multiple attributes on the same line. (@austintaylor)
87
113
  * Added: Typecasting to String before encryption (@shuber)
88
114
  * Added: `"#{attribute}?"` method. (@shuber)
89
115
 
90
- ## 1.2.0 ##
116
+ ## 1.2.0
117
+
91
118
  * Changed: General code refactoring (@shuber)
92
119
 
93
- ## 1.1.2 ##
120
+ ## 1.1.2
121
+
94
122
  * No significant changes
95
123
 
96
- ## 1.1.1 ##
124
+ ## 1.1.1
125
+
97
126
  * Changled: Updated README. (@shuber)
98
127
  * Added: `before_type_cast` alias to ActiveRecord adapter. (@shuber)
data/README.md CHANGED
@@ -1,17 +1,17 @@
1
1
  # attr_encrypted
2
- [![Build Status](https://secure.travis-ci.org/attr-encrypted/attr_encrypted.svg)](https://travis-ci.org/attr-encrypted/attr_encrypted) [![Test Coverage](https://codeclimate.com/github/attr-encrypted/attr_encrypted/badges/coverage.svg)](https://codeclimate.com/github/attr-encrypted/attr_encrypted/coverage) [![Code Climate](https://codeclimate.com/github/attr-encrypted/attr_encrypted/badges/gpa.svg)](https://codeclimate.com/github/attr-encrypted/attr_encrypted) [![Gem Version](https://badge.fury.io/rb/attr_encrypted.svg)](https://badge.fury.io/rb/attr_encrypted) [![security](https://hakiri.io/github/attr-encrypted/attr_encrypted/master.svg)](https://hakiri.io/github/attr-encrypted/attr_encrypted/master)
3
2
 
4
- Generates attr_accessors that transparently encrypt and decrypt attributes.
3
+ ![workflow](https://github.com/attr-encrypted/attr_encrypted/actions/workflows/CI.yml/badge.svg) [![Gem Version](https://badge.fury.io/rb/attr_encrypted.svg)](https://badge.fury.io/rb/attr_encrypted)
5
4
 
6
- It works with ANY class, however, you get a few extra features when you're using it with `ActiveRecord`, `DataMapper`, or `Sequel`.
5
+ Generates attr_accessors that transparently encrypt and decrypt attributes.
7
6
 
7
+ It works with ANY class, however, you get a few extra features when you're using it with `ActiveRecord` or `Sequel`.
8
8
 
9
9
  ## Installation
10
10
 
11
11
  Add attr_encrypted to your gemfile:
12
12
 
13
13
  ```ruby
14
- gem "attr_encrypted", "~> 3.0.0"
14
+ gem "attr_encrypted"
15
15
  ```
16
16
 
17
17
  Then install the gem:
@@ -22,7 +22,7 @@ Then install the gem:
22
22
 
23
23
  ## Usage
24
24
 
25
- If you're using an ORM like `ActiveRecord`, `DataMapper`, or `Sequel`, using attr_encrypted is easy:
25
+ If you're using an ORM like `ActiveRecord` or `Sequel`, using attr_encrypted is easy:
26
26
 
27
27
  ```ruby
28
28
  class User
@@ -87,6 +87,12 @@ Create or modify the table that your model uses to add a column with the `encryp
87
87
 
88
88
  You can use a string or binary column type. (See the encode option section below for more info)
89
89
 
90
+ If you use the same key for each record, add a unique index on the IV. Repeated IVs with AES-GCM (the default algorithm) allow an attacker to recover the key.
91
+
92
+ ```ruby
93
+ add_index :users, :encrypted_ssn_iv, unique: true
94
+ ```
95
+
90
96
  ### Specifying the encrypted attribute name
91
97
 
92
98
  By default, the encrypted attribute name is `encrypted_#{attribute}` (e.g. `attr_encrypted :email` would create an attribute named `encrypted_email`). So, if you're storing the encrypted attribute in the database, you need to make sure the `encrypted_#{attribute}` field exists in your table. You have a couple of options if you want to name your attribute or db column something else, see below for more details.
@@ -357,7 +363,7 @@ NOTE: This only works if all records are encrypted with the same encryption key
357
363
  __NOTE: This feature is deprecated and will be removed in the next major release.__
358
364
 
359
365
 
360
- ### DataMapper and Sequel
366
+ ### Sequel
361
367
 
362
368
  #### Default options
363
369
 
@@ -403,7 +409,7 @@ Then modify your models using attr\_encrypted to account for the changes in defa
403
409
 
404
410
  ## Upgrading from attr_encrypted v2.x to v3.x
405
411
 
406
- A bug was discovered in Encryptor v2.0.0 that inccorectly set the IV when using an AES-\*-GCM algorithm. Unfornately fixing this major security issue results in the inability to decrypt records encrypted using an AES-*-GCM algorithm from Encryptor v2.0.0. Please see [Upgrading to Encryptor v3.0.0](https://github.com/attr-encrypted/encryptor#upgrading-from-v200-to-v300) for more info.
412
+ A bug was discovered in Encryptor v2.0.0 that incorrectly set the IV when using an AES-\*-GCM algorithm. Unfornately fixing this major security issue results in the inability to decrypt records encrypted using an AES-*-GCM algorithm from Encryptor v2.0.0. Please see [Upgrading to Encryptor v3.0.0](https://github.com/attr-encrypted/encryptor#upgrading-from-v200-to-v300) for more info.
407
413
 
408
414
  It is strongly advised that you re-encrypt your data encrypted with Encryptor v2.0.0. However, you'll have to take special care to re-encrypt. To decrypt data encrypted with Encryptor v2.0.0 using an AES-\*-GCM algorithm you can use the `:v2_gcm_iv` option.
409
415
 
@@ -414,7 +420,7 @@ It is recommended that you implement a strategy to insure that you do not mix th
414
420
  attr_encrypted :ssn, key: :encryption_key, v2_gcm_iv: is_decrypting?(:ssn)
415
421
 
416
422
  def is_decrypting?(attribute)
417
- encrypted_attributes[attribute][:operation] == :decrypting
423
+ attr_encrypted_encrypted_attributes[attribute][:operation] == :decrypting
418
424
  end
419
425
  end
420
426
 
@@ -431,7 +437,7 @@ It is recommended that you implement a strategy to insure that you do not mix th
431
437
  While choosing to encrypt at the attribute level is the most secure solution, it is not without drawbacks. Namely, you cannot search the encrypted data, and because you can't search it, you can't index it either. You also can't use joins on the encrypted data. Data that is securely encrypted is effectively noise. So any operations that rely on the data not being noise will not work. If you need to do any of the aforementioned operations, please consider using database and file system encryption along with transport encryption as it moves through your stack.
432
438
 
433
439
  #### Data leaks
434
- Please also consider where your data leaks. If you're using attr_encrypted with Rails, it's highly likely that this data will enter your app as a request parameter. You'll want to be sure that you're filtering your request params from you logs or else your data is sitting in the clear in your logs. [Parameter Filtering in Rails](http://apidock.com/rails/ActionDispatch/Http/FilterParameters) Please also consider other possible leak points.
440
+ Please also consider where your data leaks. If you're using attr_encrypted with Rails, it's highly likely that this data will enter your app as a request parameter. You'll want to be sure that you're filtering your request params from your logs or else your data is sitting in the clear in your logs. [Parameter Filtering in Rails](http://apidock.com/rails/ActionDispatch/Http/FilterParameters) Please also consider other possible leak points.
435
441
 
436
442
  #### Storage requirements
437
443
  When storing your encrypted data, please consider the length requirements of the db column that you're storing the cipher text in. Older versions of Mysql attempt to 'help' you by truncating strings that are too large for the column. When this happens, you will not be able to decrypt your data. [MySQL Strict Trans](http://www.davidpashley.com/2009/02/15/silently-truncated/)
@@ -440,7 +446,7 @@ When storing your encrypted data, please consider the length requirements of the
440
446
  It is advisable to also store metadata regarding the circumstances of your encrypted data. Namely, you should store information about the key used to encrypt your data, as well as the algorithm. Having this metadata with every record will make key rotation and migrating to a new algorithm signficantly easier. It will allow you to continue to decrypt old data using the information provided in the metadata and new data can be encrypted using your new key and algorithm of choice.
441
447
 
442
448
  #### Enforcing the IV as a nonce
443
- On a related note, most alorithms require that your IV be unique for every record and key combination. You can enforce this using composite unique indexes on your IV and encryption key name/id column. [RFC 5084](https://tools.ietf.org/html/rfc5084#section-1.5)
449
+ On a related note, most algorithms require that your IV be unique for every record and key combination. You can enforce this using composite unique indexes on your IV and encryption key name/id column. [RFC 5084](https://tools.ietf.org/html/rfc5084#section-1.5)
444
450
 
445
451
  #### Unique key per record
446
452
  Lastly, while the `:per_attribute_iv_and_salt` mode is more secure than `:per_attribute_iv` mode because it uses a unique key per record, it uses a PBKDF function which introduces a huge performance hit (175x slower by my benchmarks). There are other ways of deriving a unique key per record that would be much faster.
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rake/testtask'
2
4
  require 'rdoc/task'
3
5
  require "bundler/gem_tasks"
@@ -14,55 +14,42 @@ Gem::Specification.new do |s|
14
14
  s.summary = 'Encrypt and decrypt attributes'
15
15
  s.description = 'Generates attr_accessors that encrypt and decrypt attributes transparently'
16
16
 
17
- s.authors = ['Sean Huber', 'S. Brent Faulkner', 'William Monk', 'Stephen Aghaulor']
18
- s.email = ['seah@shuber.io', 'sbfaulkner@gmail.com', 'billy.monk@gmail.com', 'saghaulor@gmail.com']
17
+ s.authors = ['Sean Huber', 'S. Brent Faulkner', 'William Monk', 'Stephen Aghaulor', 'Josh Branham', 'Mike Vastola']
18
+ s.email = ['seah@shuber.io', 'sbfaulkner@gmail.com', 'billy.monk@gmail.com', 'saghaulor@gmail.com', 'josh.php@gmail.com', 'Mike@Vasto.la']
19
19
  s.homepage = 'http://github.com/attr-encrypted/attr_encrypted'
20
20
  s.license = 'MIT'
21
21
 
22
- s.has_rdoc = false
23
- s.rdoc_options = ['--line-numbers', '--inline-source', '--main', 'README.rdoc']
24
-
25
22
  s.require_paths = ['lib']
26
23
 
27
24
  s.files = `git ls-files`.split("\n")
28
25
  s.test_files = `git ls-files -- test/*`.split("\n")
29
26
 
30
- s.required_ruby_version = '>= 2.0.0'
27
+ s.required_ruby_version = '>= 2.6.0'
31
28
 
32
29
  s.add_dependency('encryptor', ['~> 3.0.0'])
33
30
  # support for testing with specific active record version
34
31
  activerecord_version = if ENV.key?('ACTIVERECORD')
35
- "~> #{ENV['ACTIVERECORD']}"
32
+ "~> #{ENV['ACTIVERECORD']}.0"
36
33
  else
37
34
  '>= 2.0.0'
38
35
  end
39
36
  s.add_development_dependency('activerecord', activerecord_version)
40
37
  s.add_development_dependency('actionpack', activerecord_version)
41
- s.add_development_dependency('datamapper')
42
38
  s.add_development_dependency('rake')
43
39
  s.add_development_dependency('minitest')
44
40
  s.add_development_dependency('sequel')
45
- if RUBY_VERSION < '2.1.0'
46
- s.add_development_dependency('nokogiri', '< 1.7.0')
47
- s.add_development_dependency('public_suffix', '< 3.0.0')
48
- end
49
41
  if defined?(RUBY_ENGINE) && RUBY_ENGINE.to_sym == :jruby
50
42
  s.add_development_dependency('activerecord-jdbcsqlite3-adapter')
51
43
  s.add_development_dependency('jdbc-sqlite3', '< 3.8.7') # 3.8.7 is nice and broke
52
44
  else
53
- s.add_development_dependency('sqlite3')
45
+ s.add_development_dependency('sqlite3', '= 1.5.4')
54
46
  end
55
47
  s.add_development_dependency('dm-sqlite-adapter')
48
+ s.add_development_dependency('pry')
56
49
  s.add_development_dependency('simplecov')
57
50
  s.add_development_dependency('simplecov-rcov')
58
- s.add_development_dependency("codeclimate-test-reporter", '<= 0.6.0')
59
-
60
- s.cert_chain = ['certs/saghaulor.pem']
61
- s.signing_key = File.expand_path("~/.ssh/gem-private_key.pem") if $0 =~ /gem\z/
62
51
 
63
- s.post_install_message = "\n\n\nWARNING: Several insecure default options and features were deprecated in attr_encrypted v2.0.0.\n
64
- Additionally, there was a bug in Encryptor v2.0.0 that insecurely encrypted data when using an AES-*-GCM algorithm.\n
65
- This bug was fixed but introduced breaking changes between v2.x and v3.x.\n
66
- Please see the README for more information regarding upgrading to attr_encrypted v3.0.0.\n\n\n"
52
+ s.post_install_message = "\n\n\nWARNING: Using `#encrypted_attributes` is no longer supported. Instead, use `#attr_encrypted_encrypted_attributes` to avoid
53
+ collision with Active Record 7 native encryption.\n\n\n"
67
54
 
68
55
  end
@@ -0,0 +1 @@
1
+ 4f0682604714ed4599cf00771ad27e82f0b51b0ed8644af51a43d21fbe129b59
@@ -0,0 +1 @@
1
+ dc757a50c53175dc05adbfccb25b536f7f1a060c7bd626bfc72ff577479c6fe28d3b65747033276bd4bce3dad741b67235f3e01ea61409f6c67959da65df445b
@@ -1,7 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  if defined?(ActiveRecord::Base)
2
4
  module AttrEncrypted
3
5
  module Adapters
4
6
  module ActiveRecord
7
+ RAILS_VERSION = Gem::Version.new(::ActiveRecord::VERSION::STRING).freeze
8
+
5
9
  def self.extended(base) # :nodoc:
6
10
  base.class_eval do
7
11
 
@@ -9,7 +13,7 @@ if defined?(ActiveRecord::Base)
9
13
  alias_method :reload_without_attr_encrypted, :reload
10
14
  def reload(*args, &block)
11
15
  result = reload_without_attr_encrypted(*args, &block)
12
- self.class.encrypted_attributes.keys.each do |attribute_name|
16
+ self.class.attr_encrypted_encrypted_attributes.keys.each do |attribute_name|
13
17
  instance_variable_set("@#{attribute_name}", nil)
14
18
  end
15
19
  result
@@ -25,12 +29,12 @@ if defined?(ActiveRecord::Base)
25
29
  def perform_attribute_assignment(method, new_attributes, *args)
26
30
  return if new_attributes.blank?
27
31
 
28
- send method, new_attributes.reject { |k, _| self.class.encrypted_attributes.key?(k.to_sym) }, *args
29
- send method, new_attributes.reject { |k, _| !self.class.encrypted_attributes.key?(k.to_sym) }, *args
32
+ send method, new_attributes.reject { |k, _| self.class.attr_encrypted_encrypted_attributes.key?(k.to_sym) }, *args
33
+ send method, new_attributes.reject { |k, _| !self.class.attr_encrypted_encrypted_attributes.key?(k.to_sym) }, *args
30
34
  end
31
35
  private :perform_attribute_assignment
32
36
 
33
- if ::ActiveRecord::VERSION::STRING > "3.1"
37
+ if Gem::Requirement.new('> 3.1').satisfied_by?(RAILS_VERSION)
34
38
  alias_method :assign_attributes_without_attr_encrypted, :assign_attributes
35
39
  def assign_attributes(*args)
36
40
  perform_attribute_assignment :assign_attributes_without_attr_encrypted, *args
@@ -51,21 +55,15 @@ if defined?(ActiveRecord::Base)
51
55
  super
52
56
  options = attrs.extract_options!
53
57
  attr = attrs.pop
54
- attribute attr if ::ActiveRecord::VERSION::STRING >= "5.1.0"
55
- options.merge! encrypted_attributes[attr]
58
+ attribute attr
59
+ options.merge! attr_encrypted_encrypted_attributes[attr]
56
60
 
57
61
  define_method("#{attr}_was") do
58
62
  attribute_was(attr)
59
63
  end
60
64
 
61
- if ::ActiveRecord::VERSION::STRING >= "4.1"
62
- define_method("#{attr}_changed?") do |options = {}|
63
- attribute_changed?(attr, options)
64
- end
65
- else
66
- define_method("#{attr}_changed?") do
67
- attribute_changed?(attr)
68
- end
65
+ define_method("#{attr}_changed?") do |options = {}|
66
+ attribute_changed?(attr, **options)
69
67
  end
70
68
 
71
69
  define_method("#{attr}_change") do
@@ -73,6 +71,19 @@ if defined?(ActiveRecord::Base)
73
71
  end
74
72
 
75
73
  define_method("#{attr}_with_dirtiness=") do |value|
74
+ ## Source: https://github.com/priyankatapar/attr_encrypted/commit/7e8702bd5418c927a39d8dd72c0adbea522d5663
75
+ # In ActiveRecord 5.2+, due to changes to the way virtual
76
+ # attributes are handled, @attributes[attr].value is nil which
77
+ # breaks attribute_was. Setting it here returns us to the expected
78
+ # behavior.
79
+ if RAILS_VERSION >= Gem::Version.new(5.2)
80
+ # This is needed support attribute_was before a record has
81
+ # been saved
82
+ @attributes.write_cast_value(attr.to_s, __send__(attr)) if value != __send__(attr)
83
+ # This is needed to support attribute_was after a record has
84
+ # been saved
85
+ @attributes.write_from_user(attr.to_s, value) if value != __send__(attr)
86
+ end
76
87
  attribute_will_change!(attr) if value != __send__(attr)
77
88
  __send__("#{attr}_without_dirtiness=", value)
78
89
  end
@@ -120,10 +131,10 @@ if defined?(ActiveRecord::Base)
120
131
  if match = /^(find|scoped)_(all_by|by)_([_a-zA-Z]\w*)$/.match(method.to_s)
121
132
  attribute_names = match.captures.last.split('_and_')
122
133
  attribute_names.each_with_index do |attribute, index|
123
- if attr_encrypted?(attribute) && encrypted_attributes[attribute.to_sym][:mode] == :single_iv_and_salt
134
+ if attr_encrypted?(attribute) && attr_encrypted_encrypted_attributes[attribute.to_sym][:mode] == :single_iv_and_salt
124
135
  args[index] = send("encrypt_#{attribute}", args[index])
125
136
  warn "DEPRECATION WARNING: This feature will be removed in the next major release."
126
- attribute_names[index] = encrypted_attributes[attribute.to_sym][:attribute]
137
+ attribute_names[index] = attr_encrypted_encrypted_attributes[attribute.to_sym][:attribute]
127
138
  end
128
139
  end
129
140
  method = "#{match.captures[0]}_#{match.captures[1]}_#{attribute_names.join('_and_')}".to_sym
@@ -134,6 +145,8 @@ if defined?(ActiveRecord::Base)
134
145
  end
135
146
  end
136
147
 
137
- ActiveRecord::Base.extend AttrEncrypted
138
- ActiveRecord::Base.extend AttrEncrypted::Adapters::ActiveRecord
148
+ ActiveSupport.on_load(:active_record) do
149
+ extend AttrEncrypted
150
+ extend AttrEncrypted::Adapters::ActiveRecord
151
+ end
139
152
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  if defined?(Sequel)
2
4
  module AttrEncrypted
3
5
  module Adapters
@@ -11,4 +13,4 @@ if defined?(Sequel)
11
13
 
12
14
  Sequel::Model.extend AttrEncrypted
13
15
  Sequel::Model.extend AttrEncrypted::Adapters::Sequel
14
- end
16
+ end
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AttrEncrypted
2
4
  # Contains information about this gem's version
3
5
  module Version
4
- MAJOR = 3
6
+ MAJOR = 4
5
7
  MINOR = 1
6
8
  PATCH = 0
7
9
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'encryptor'
2
4
 
3
5
  # Adds attr_accessors that encrypt and decrypt an object's attributes
@@ -8,7 +10,7 @@ module AttrEncrypted
8
10
  base.class_eval do
9
11
  include InstanceMethods
10
12
  attr_writer :attr_encrypted_options
11
- @attr_encrypted_options, @encrypted_attributes = {}, {}
13
+ @attr_encrypted_options, @attr_encrypted_encrypted_attributes = {}, {}
12
14
  end
13
15
  end
14
16
 
@@ -52,7 +54,7 @@ module AttrEncrypted
52
54
  # string instead of just 'true'. See
53
55
  # http://www.ruby-doc.org/core/classes/Array.html#M002245
54
56
  # for more encoding directives.
55
- # Defaults to false unless you're using it with ActiveRecord, DataMapper, or Sequel.
57
+ # Defaults to false unless you're using it with ActiveRecord or Sequel.
56
58
  #
57
59
  # encode_iv: Defaults to true.
58
60
 
@@ -158,11 +160,11 @@ module AttrEncrypted
158
160
  end
159
161
 
160
162
  define_method(attribute) do
161
- instance_variable_get("@#{attribute}") || instance_variable_set("@#{attribute}", decrypt(attribute, send(encrypted_attribute_name)))
163
+ instance_variable_get("@#{attribute}") || instance_variable_set("@#{attribute}", attr_encrypted_decrypt(attribute, send(encrypted_attribute_name)))
162
164
  end
163
165
 
164
166
  define_method("#{attribute}=") do |value|
165
- send("#{encrypted_attribute_name}=", encrypt(attribute, value))
167
+ send("#{encrypted_attribute_name}=", attr_encrypted_encrypt(attribute, value))
166
168
  instance_variable_set("@#{attribute}", value)
167
169
  end
168
170
 
@@ -171,7 +173,7 @@ module AttrEncrypted
171
173
  value.respond_to?(:empty?) ? !value.empty? : !!value
172
174
  end
173
175
 
174
- encrypted_attributes[attribute.to_sym] = options.merge(attribute: encrypted_attribute_name)
176
+ self.attr_encrypted_encrypted_attributes[attribute.to_sym] = options.merge(attribute: encrypted_attribute_name)
175
177
  end
176
178
  end
177
179
 
@@ -221,7 +223,7 @@ module AttrEncrypted
221
223
  # User.attr_encrypted?(:name) # false
222
224
  # User.attr_encrypted?(:email) # true
223
225
  def attr_encrypted?(attribute)
224
- encrypted_attributes.has_key?(attribute.to_sym)
226
+ attr_encrypted_encrypted_attributes.has_key?(attribute.to_sym)
225
227
  end
226
228
 
227
229
  # Decrypts a value for the attribute specified
@@ -232,9 +234,9 @@ module AttrEncrypted
232
234
  # attr_encrypted :email
233
235
  # end
234
236
  #
235
- # email = User.decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING')
236
- def decrypt(attribute, encrypted_value, options = {})
237
- options = encrypted_attributes[attribute.to_sym].merge(options)
237
+ # email = User.attr_encrypted_decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING')
238
+ def attr_encrypted_decrypt(attribute, encrypted_value, options = {})
239
+ options = attr_encrypted_encrypted_attributes[attribute.to_sym].merge(options)
238
240
  if options[:if] && !options[:unless] && not_empty?(encrypted_value)
239
241
  encrypted_value = encrypted_value.unpack(options[:encode]).first if options[:encode]
240
242
  value = options[:encryptor].send(options[:decrypt_method], options.merge!(value: encrypted_value))
@@ -258,9 +260,9 @@ module AttrEncrypted
258
260
  # attr_encrypted :email
259
261
  # end
260
262
  #
261
- # encrypted_email = User.encrypt(:email, 'test@example.com')
262
- def encrypt(attribute, value, options = {})
263
- options = encrypted_attributes[attribute.to_sym].merge(options)
263
+ # encrypted_email = User.attr_encrypted_encrypt(:email, 'test@example.com')
264
+ def attr_encrypted_encrypt(attribute, value, options = {})
265
+ options = attr_encrypted_encrypted_attributes[attribute.to_sym].merge(options)
264
266
  if options[:if] && !options[:unless] && (options[:allow_empty_value] || not_empty?(value))
265
267
  value = options[:marshal] ? options[:marshaler].send(options[:dump_method], value) : value.to_s
266
268
  encrypted_value = options[:encryptor].send(options[:encrypt_method], options.merge!(value: value))
@@ -284,9 +286,9 @@ module AttrEncrypted
284
286
  # attr_encrypted :email, key: 'my secret key'
285
287
  # end
286
288
  #
287
- # User.encrypted_attributes # { email: { attribute: 'encrypted_email', key: 'my secret key' } }
288
- def encrypted_attributes
289
- @encrypted_attributes ||= superclass.encrypted_attributes.dup
289
+ # User.attr_encrypted_encrypted_attributes # { email: { attribute: 'encrypted_email', key: 'my secret key' } }
290
+ def attr_encrypted_encrypted_attributes
291
+ @attr_encrypted_encrypted_attributes ||= superclass.attr_encrypted_encrypted_attributes.dup
290
292
  end
291
293
 
292
294
  # Forwards calls to :encrypt_#{attribute} or :decrypt_#{attribute} to the corresponding encrypt or decrypt method
@@ -301,7 +303,7 @@ module AttrEncrypted
301
303
  # User.encrypt_email('SOME_ENCRYPTED_EMAIL_STRING')
302
304
  def method_missing(method, *arguments, &block)
303
305
  if method.to_s =~ /^((en|de)crypt)_(.+)$/ && attr_encrypted?($3)
304
- send($1, $3, *arguments)
306
+ send("attr_encrypted_#{$1}", $3, *arguments)
305
307
  else
306
308
  super
307
309
  end
@@ -323,10 +325,10 @@ module AttrEncrypted
323
325
  #
324
326
  # @user = User.new('some-secret-key')
325
327
  # @user.decrypt(:email, 'SOME_ENCRYPTED_EMAIL_STRING')
326
- def decrypt(attribute, encrypted_value)
327
- encrypted_attributes[attribute.to_sym][:operation] = :decrypting
328
- encrypted_attributes[attribute.to_sym][:value_present] = self.class.not_empty?(encrypted_value)
329
- self.class.decrypt(attribute, encrypted_value, evaluated_attr_encrypted_options_for(attribute))
328
+ def attr_encrypted_decrypt(attribute, encrypted_value)
329
+ attr_encrypted_encrypted_attributes[attribute.to_sym][:operation] = :decrypting
330
+ attr_encrypted_encrypted_attributes[attribute.to_sym][:value_present] = self.class.not_empty?(encrypted_value)
331
+ self.class.attr_encrypted_decrypt(attribute, encrypted_value, evaluated_attr_encrypted_options_for(attribute))
330
332
  end
331
333
 
332
334
  # Encrypts a value for the attribute specified using options evaluated in the current object's scope
@@ -343,18 +345,22 @@ module AttrEncrypted
343
345
  # end
344
346
  #
345
347
  # @user = User.new('some-secret-key')
346
- # @user.encrypt(:email, 'test@example.com')
347
- def encrypt(attribute, value)
348
- encrypted_attributes[attribute.to_sym][:operation] = :encrypting
349
- encrypted_attributes[attribute.to_sym][:value_present] = self.class.not_empty?(value)
350
- self.class.encrypt(attribute, value, evaluated_attr_encrypted_options_for(attribute))
348
+ # @user.attr_encrypted_encrypt(:email, 'test@example.com')
349
+ def attr_encrypted_encrypt(attribute, value)
350
+ attr_encrypted_encrypted_attributes[attribute.to_sym][:operation] = :encrypting
351
+ attr_encrypted_encrypted_attributes[attribute.to_sym][:value_present] = self.class.not_empty?(value)
352
+ self.class.attr_encrypted_encrypt(attribute, value, evaluated_attr_encrypted_options_for(attribute))
351
353
  end
352
354
 
353
355
  # Copies the class level hash of encrypted attributes with virtual attribute names as keys
354
356
  # and their corresponding options as values to the instance
355
357
  #
356
- def encrypted_attributes
357
- @encrypted_attributes ||= self.class.encrypted_attributes.dup
358
+ def attr_encrypted_encrypted_attributes
359
+ @attr_encrypted_encrypted_attributes ||= begin
360
+ duplicated= {}
361
+ self.class.attr_encrypted_encrypted_attributes.map { |key, value| duplicated[key] = value.dup }
362
+ duplicated
363
+ end
358
364
  end
359
365
 
360
366
  protected
@@ -362,15 +368,21 @@ module AttrEncrypted
362
368
  # Returns attr_encrypted options evaluated in the current object's scope for the attribute specified
363
369
  def evaluated_attr_encrypted_options_for(attribute)
364
370
  evaluated_options = Hash.new
365
- attribute_option_value = encrypted_attributes[attribute.to_sym][:attribute]
366
- encrypted_attributes[attribute.to_sym].map do |option, value|
367
- evaluated_options[option] = evaluate_attr_encrypted_option(value)
371
+ attributes = attr_encrypted_encrypted_attributes[attribute.to_sym]
372
+ attribute_option_value = attributes[:attribute]
373
+
374
+ [:if, :unless, :value_present, :allow_empty_value].each do |option|
375
+ evaluated_options[option] = evaluate_attr_encrypted_option(attributes[option])
368
376
  end
369
377
 
370
378
  evaluated_options[:attribute] = attribute_option_value
371
379
 
372
380
  evaluated_options.tap do |options|
373
381
  if options[:if] && !options[:unless] && options[:value_present] || options[:allow_empty_value]
382
+ (attributes.keys - evaluated_options.keys).each do |option|
383
+ options[option] = evaluate_attr_encrypted_option(attributes[option])
384
+ end
385
+
374
386
  unless options[:mode] == :single_iv_and_salt
375
387
  load_iv_for_attribute(attribute, options)
376
388
  end