attr_digest 1.2.0 → 2.0.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -0
- data/README.md +75 -11
- data/lib/attr_digest/attr_digest.rb +76 -11
- data/lib/attr_digest/version.rb +2 -2
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eaa4ae880f27d1904cc66b8d9f7186616ab8054b
|
4
|
+
data.tar.gz: b00e5deaa044a148f202cc1400bce150083b995a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
[](https://travis-ci.org/brightcommerce/attr_digest)
|
3
3
|
[](https://codecov.io/github/brightcommerce/attr_digest?branch=master)
|
4
4
|
[](https://github.com/brightcommerce/attr_digest)
|
5
|
-
[](https://github.com/
|
5
|
+
[](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.
|
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
|
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
|
-
####
|
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
|
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 = {
|
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
|
-
|
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
|
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
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
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
|
-
|
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 :
|
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
|
data/lib/attr_digest/version.rb
CHANGED
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:
|
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-
|
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.
|
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.
|
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
|
174
|
+
summary: AttrDigest v2.0.0
|
175
175
|
test_files: []
|