attr_digest 1.2.0 → 2.0.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
2
  SHA1:
3
- metadata.gz: 63cdafa1a0c9fb93a2faf186e6cfc34a9cbb3a82
4
- data.tar.gz: 92768dd263b1ef29f66b6634a79373164ed43534
3
+ metadata.gz: eaa4ae880f27d1904cc66b8d9f7186616ab8054b
4
+ data.tar.gz: b00e5deaa044a148f202cc1400bce150083b995a
5
5
  SHA512:
6
- metadata.gz: c9135190b059ebddd9bae632614f5c787675d0cb627679b081e25f387d99bbed91217606e280ad1a9863898fb249ce831085dd2fcc1d03487a6a9b7d0332c7f7
7
- data.tar.gz: ce9daa1a773fc71b8c2a9741aa91cece52490e15ee7be7bf5edc5e026c48d44e452f6a127b8007fb6acf8dc686b9a9ba21ce7abb00566d1ee39b3c4cdb9d3ee3
6
+ metadata.gz: 4f2b05367ce886a884cf6b7a0c508efbfd212bf72f14e2b5594debd46dc8184fbdde02801018925359e8acd870094e07a99a9658ac5180159ab3102c7b8022ed
7
+ data.tar.gz: 4b341a00e8c34d9bce2a1c493238d1f9bd26f508956e87f63fd5e183bfcf22841cc0139ed98a308a0c152c1eada81e03fe36594c6e7fba14947d27dd103cde6c
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Change Log
2
2
 
3
+ ##v2.0.0
4
+ - Added Ruby 2.3.1 to Travis CI configuration file.
5
+ - Updated README.
6
+ - Added length validator option.
7
+ - Added format validation option.
8
+ - Refactored validation method name.
9
+ - Updated Argon2 dependency to V1.1.1.
10
+ - Added secret key option.
11
+ - Renamed spec classes and factory names.
12
+ - Added time_cost and memory_cost to attr_digest class method and AttrDigest constant.
13
+ - Coerce values to string, permit blank digest with tests.
14
+ - Added NoDigestException and tests.
15
+
3
16
  ##v1.2.0
4
17
  - Updated ActiveSupport and ActiveRecord between 4.2.6 and v5.0.0.
5
18
 
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
  [![Build Status](https://travis-ci.org/brightcommerce/attr_digest.svg?branch=master)](https://travis-ci.org/brightcommerce/attr_digest)
3
3
  [![codecov.io](https://codecov.io/github/brightcommerce/attr_digest/coverage.svg?branch=master)](https://codecov.io/github/brightcommerce/attr_digest?branch=master)
4
4
  [![HitCount](https://hitt.herokuapp.com/brightcommerce/attr_digest.svg)](https://github.com/brightcommerce/attr_digest)
5
- [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/dwyl/esta/issues)
5
+ [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/brightcommerce/attr_digest/pulls)
6
6
 
7
7
  # AttrDigest
8
8
 
@@ -19,7 +19,7 @@ This Gem uses the [Ruby Argon2 Gem](https://github.com/technion/ruby-argon2) whi
19
19
  To install add the following line to your `Gemfile`:
20
20
 
21
21
  ``` ruby
22
- gem 'attr_digest',
22
+ gem 'attr_digest'
23
23
  ```
24
24
 
25
25
  And run `bundle install`.
@@ -29,7 +29,7 @@ And run `bundle install`.
29
29
  Runtime:
30
30
  - activerecord (>= 4.2.6, ~> 5.0.0)
31
31
  - activesupport (>= 4.2.6, ~> 5.0.0)
32
- - argon2 (~> 1.1.0)
32
+ - argon2 (~> 1.1.1)
33
33
 
34
34
  Development/Test:
35
35
  - rake (~> 10.5)
@@ -40,11 +40,10 @@ Development/Test:
40
40
 
41
41
  ## Compatibility
42
42
 
43
- Tested with Ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin15] against ActiveRecord 5.0.0 on Mac OS X El Capitan 10.11.5 (15F34).
43
+ Tested with Ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin15] against ActiveRecord 5.0.0 on macOS Sierra 10.12.1 (16B2555).
44
44
 
45
45
  Argon2 requires Ruby 2.2 minimum and an OS platform that supports Ruby FFI Bindings, so unfortunately Windows is out.
46
46
 
47
-
48
47
  ## Usage
49
48
 
50
49
  Attributes to be digested are declared using the `attr_digest` class method in your model:
@@ -98,7 +97,34 @@ If you prefer to skip confirmations for the attribute you are hashing, you can p
98
97
  attr_digest :security_answer, confirmation: false
99
98
  ```
100
99
 
101
- #### Protected Digest Setter
100
+ #### Format
101
+
102
+ You can ensure the attribute you are hashing matches a given regular expression by passing a `format` option:
103
+
104
+ ```ruby
105
+ attr_digest :password, format: { with: /\A[a-zA-Z]+\z/, message: "only allows letters" }
106
+ ```
107
+
108
+ AttrDigest adds the Rails format validator and passes the options hash through as is. See [Active Record Validations format validator](http://edgeguides.rubyonrails.org/active_record_validations.html#format) for options you can pass to the `format` options hash.
109
+
110
+ **NOTE:** The `format` option is not affected by the `validations` option. Adding the `format` option will add a Rails format validator *regardless* of whether the `validations` option is set to `true` or `false`.
111
+
112
+ #### Length
113
+
114
+ You can ensure the attribute your are hashing meets certain length criteria by passing a `length` option:
115
+
116
+ ```ruby
117
+ attr_digest :password, length: { minimum: 5 }
118
+ attr_digest :password, length: { maximum: 10 }
119
+ attr_digest :password, length: { in: 5..10 }
120
+ attr_digest :password, length: { is: 8 }
121
+ ```
122
+
123
+ AttrDigest adds the Rails length validator and passes the options hash through as is. See [Active Record Validations length validator](http://edgeguides.rubyonrails.org/active_record_validations.html#length) for options you can pass to the `length` options hash.
124
+
125
+ **NOTE:** The `length` option is not affected by the `validations` option. Adding the `length` option will add a Rails length validator *regardless* of whether the `validations` option is set to `true` or `false`.
126
+
127
+ ### Protected Digest Setter
102
128
 
103
129
  If you want to prevent the attribute's digest being set directly, you can include the `protected` option:
104
130
 
@@ -108,9 +134,47 @@ attr_digest :security_answer, protected: true
108
134
 
109
135
  The attribute's digest is *not* protected from direct setting by default.
110
136
 
137
+ ### Time and Memory Costs
138
+
139
+ **AttrDigest** sets a default time and memory cost and expects the following minimum and maximum values:
140
+
141
+ Option | Minimum Value | Maximum Value | Default Value
142
+ --- | --- | --- | ---
143
+ :time_cost | 1 | 10 | 2
144
+ :memory_cost | 1 | 31 | 16
145
+
146
+ You can change the global defaults by setting the cost options directly on the `AttrDigest` class:
147
+
148
+ ```ruby
149
+ AttrDigest.time_cost = 3
150
+ AttrDigest.memory_cost = 12
151
+ ```
152
+
153
+ You can also change the time and memory cost for a specific attribute by passing the options to the `attr_digest` class method in your model:
154
+
155
+ ```ruby
156
+ attr_digest :security_answer, time_cost: 3, memory_cost: 12
157
+ ```
158
+
159
+ ### Secret Key
160
+
161
+ Argon2 supports an optional secret key value. This should be stored securely on your server, such as alongside your database credentials. Hashes generated with a secret key will only validate when presented that secret.
162
+
163
+ You can set the optional secret key globally by setting the `secret` attribute on the `AttrDigest` class:
164
+
165
+ ```ruby
166
+ AttrDigest.secret = Rails.application.secrets.secret_key_base
167
+ ```
168
+
169
+ You can also set the optional secret key for a specific attribute by passing the `:secret` option to the `attr_digest` class method in your model:
170
+
171
+ ```ruby
172
+ attr_digest :security_answer, secret: Rails.application.secrets.secret_key_base
173
+ ```
174
+
111
175
  ## Tests
112
176
 
113
- Tests are written using Rspec, FactoryGirl and Sqlite3. There are 31 examples with 100% code coverage.
177
+ Tests are written using Rspec, FactoryGirl and Sqlite3. There are 52 examples with 100% code coverage.
114
178
 
115
179
  To run the tests, execute the default rake task:
116
180
 
@@ -118,10 +182,6 @@ To run the tests, execute the default rake task:
118
182
  bundle exec rake
119
183
  ```
120
184
 
121
- ## Roadmap
122
-
123
- I would like to add the ability to pass a `secret` to the Argon2 hasher. This functionality exists in the Ruby Argon2 Gem.
124
-
125
185
  ## Contributing
126
186
 
127
187
  1. Fork it
@@ -134,6 +194,10 @@ I would like to add the ability to pass a `secret` to the Argon2 hasher. This fu
134
194
 
135
195
  I would like to thank [Panayotis Matsinopoulos](http://www.matsinopoulos.gr) for his [has_secure_attribute](https://github.com/pmatsinopoulos/has_secure_attribute) gem which provided a lot of the inspiration and framework for **AttrDigest**.
136
196
 
197
+ I would also like to thank [Lawrence Sproul](https://github.com/Lawrence-Sproul) for bringing to light some potential error conditions, providing the motivation to make the gem feature complete and the inspiration for additional validation options.
198
+
199
+ This gem was written and is maintained by [Jurgen Jocubeit](https://github.com/JurgenJocubeit), CEO and President Brightcommerce, Inc.
200
+
137
201
  ## License
138
202
 
139
203
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -5,44 +5,99 @@ require 'active_support/all'
5
5
  module AttrDigest
6
6
  extend ActiveSupport::Concern
7
7
 
8
+ class Error < ::StandardError
9
+ end
10
+
11
+ class NoDigestException < Error
12
+ end
13
+
14
+ class InvalidMemoryCost < Error
15
+ end
16
+
17
+ class InvalidTimeCost < Error
18
+ end
19
+
8
20
  class << self
9
21
  attr_accessor :time_cost
10
22
  attr_accessor :memory_cost
23
+ attr_accessor :secret
11
24
  end
12
25
 
13
- self.time_cost = 2
14
- self.memory_cost = 16
26
+ self.time_cost = 2 # 1..10
27
+ self.memory_cost = 16 # 1..31
15
28
 
16
29
  module ClassMethods
17
30
  def attr_digest(meth, *args, &block)
18
31
  attribute_sym = meth.to_sym
19
32
  attr_reader attribute_sym
20
33
 
21
- options = { validations: true, protected: false, case_sensitive: true, confirmation: true }
34
+ options = {
35
+ validations: true,
36
+ protected: false,
37
+ case_sensitive: true,
38
+ confirmation: true,
39
+ time_cost: AttrDigest.time_cost,
40
+ memory_cost: AttrDigest.memory_cost,
41
+ secret: AttrDigest.secret
42
+ }
22
43
  options.merge! args[0] unless args.blank?
23
44
 
24
45
  if options[:validations]
25
- confirm attribute_sym if options[:confirmation]
46
+ add_confirmation_validation(attribute_sym) if options[:confirmation]
26
47
  validates attribute_sym, presence: true, on: :create
27
48
  before_create { raise "#{attribute_sym}_digest missing on new record" if send("#{attribute_sym}_digest").blank? }
28
49
  end
29
50
 
51
+ if options[:format]
52
+ add_format_validation(attribute_sym, options)
53
+ end
54
+
55
+ if options[:length]
56
+ add_length_validation(attribute_sym, options)
57
+ end
58
+
30
59
  define_setter(attribute_sym, options)
31
60
  protect_setter(attribute_sym) if options[:protected]
32
61
  define_authenticate_method(attribute_sym, options)
33
62
  end
34
63
 
35
- def confirm(attribute_sym)
64
+ def add_confirmation_validation(attribute_sym)
36
65
  validates attribute_sym, confirmation: true, if: lambda { |m| m.send(attribute_sym).present? }
37
66
  validates "#{attribute_sym}_confirmation".to_sym, presence: true, if: lambda { |m| m.send(attribute_sym).present? }
38
67
  end
39
68
 
69
+ def add_format_validation(attribute_sym, options)
70
+ validates attribute_sym, format: options[:format], if: lambda { |m| m.send(attribute_sym).present? }
71
+ end
72
+
73
+ def add_length_validation(attribute_sym, options)
74
+ validates attribute_sym, length: options[:length], if: lambda { |m| m.send(attribute_sym).present? }
75
+ end
76
+
40
77
  def define_setter(attribute_sym, options)
41
78
  define_method "#{attribute_sym.to_s}=" do |unencrypted_value|
42
- unless unencrypted_value.blank?
43
- instance_variable_set("@#{attribute_sym.to_s}".to_sym, unencrypted_value)
44
- password = Argon2::Password.new(t_cost: AttrDigest.time_cost, m_cost: AttrDigest.memory_cost)
45
- send("#{attribute_sym.to_s}_digest=".to_sym, password.create(options[:case_sensitive] ? unencrypted_value : unencrypted_value.downcase))
79
+ if options[:memory_cost] < 1 || options[:memory_cost] > 31
80
+ raise InvalidMemoryCost.new("Invalid memory cost, min 1 and max 31")
81
+ end
82
+
83
+ if options[:time_cost] < 1 || options[:time_cost] > 31
84
+ raise InvalidTimeCost.new("Invalid time cost, min 1 and max 10")
85
+ end
86
+
87
+ password = if options[:secret]
88
+ Argon2::Password.new(t_cost: options[:time_cost], m_cost: options[:memory_cost], secret: options[:secret])
89
+ else
90
+ Argon2::Password.new(t_cost: options[:time_cost], m_cost: options[:memory_cost])
91
+ end
92
+
93
+ if options[:validations] == true
94
+ unless unencrypted_value.blank?
95
+ instance_variable_set("@#{attribute_sym.to_s}".to_sym, unencrypted_value)
96
+ send("#{attribute_sym.to_s}_digest=".to_sym, password.create(options[:case_sensitive] ? unencrypted_value : unencrypted_value.downcase))
97
+ end
98
+ else
99
+ instance_variable_set("@#{attribute_sym.to_s}".to_sym, "#{unencrypted_value}")
100
+ send("#{attribute_sym.to_s}_digest=".to_sym, password.create(options[:case_sensitive] ? "#{unencrypted_value}" : "#{unencrypted_value}".downcase))
46
101
  end
47
102
  end
48
103
  end
@@ -56,12 +111,22 @@ module AttrDigest
56
111
 
57
112
  def define_authenticate_method(attribute_sym, options)
58
113
  define_method "authenticate_#{attribute_sym}" do |value|
59
- Argon2::Password.verify_password((options[:case_sensitive] ? value : value.downcase), send("#{attribute_sym}_digest"))
114
+ digest = send("#{attribute_sym}_digest")
115
+ if digest.blank?
116
+ raise NoDigestException.new("Digest for #{attribute_sym} is nil, there is nothing to authenticate with.")
117
+ end
118
+ if options[:secret]
119
+ Argon2::Password.verify_password((options[:case_sensitive] ? "#{value}" : "#{value}".downcase), digest, options[:secret])
120
+ else
121
+ Argon2::Password.verify_password((options[:case_sensitive] ? "#{value}" : "#{value}".downcase), digest)
122
+ end
60
123
  end
61
124
  end
62
125
 
63
126
  protected :attr_digest
64
- protected :confirm
127
+ protected :add_confirmation_validation
128
+ protected :add_format_validation
129
+ protected :add_length_validation
65
130
  protected :define_setter
66
131
  protected :protect_setter
67
132
  protected :define_authenticate_method
@@ -1,7 +1,7 @@
1
1
  module AttrDigest
2
2
  module VERSION
3
- MAJOR = 1
4
- MINOR = 2
3
+ MAJOR = 2
4
+ MINOR = 0
5
5
  TINY = 0
6
6
  PRE = nil
7
7
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: attr_digest
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jurgen Jocubeit
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-07-03 00:00:00.000000000 Z
11
+ date: 2016-11-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -56,14 +56,14 @@ dependencies:
56
56
  requirements:
57
57
  - - "~>"
58
58
  - !ruby/object:Gem::Version
59
- version: 1.1.0
59
+ version: 1.1.1
60
60
  type: :runtime
61
61
  prerelease: false
62
62
  version_requirements: !ruby/object:Gem::Requirement
63
63
  requirements:
64
64
  - - "~>"
65
65
  - !ruby/object:Gem::Version
66
- version: 1.1.0
66
+ version: 1.1.1
67
67
  - !ruby/object:Gem::Dependency
68
68
  name: rake
69
69
  requirement: !ruby/object:Gem::Requirement
@@ -171,5 +171,5 @@ rubyforge_project:
171
171
  rubygems_version: 2.5.1
172
172
  signing_key:
173
173
  specification_version: 4
174
- summary: AttrDigest v1.2.0
174
+ summary: AttrDigest v2.0.0
175
175
  test_files: []