authlogic 3.3.0 → 3.4.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 +7 -0
- data/.gitignore +3 -1
- data/.travis.yml +27 -0
- data/CONTRIBUTING.md +10 -0
- data/Gemfile.lock +46 -28
- data/History +10 -0
- data/README.rdoc +2 -0
- data/Rakefile +0 -13
- data/authlogic.gemspec +8 -7
- data/lib/authlogic/acts_as_authentic/email.rb +1 -1
- data/lib/authlogic/acts_as_authentic/login.rb +12 -13
- data/lib/authlogic/acts_as_authentic/password.rb +47 -47
- data/lib/authlogic/acts_as_authentic/perishable_token.rb +1 -1
- data/lib/authlogic/acts_as_authentic/persistence_token.rb +1 -1
- data/lib/authlogic/authenticates_many/base.rb +1 -1
- data/lib/authlogic/controller_adapters/sinatra_adapter.rb +1 -1
- data/lib/authlogic/crypto_providers/bcrypt.rb +19 -18
- data/lib/authlogic/crypto_providers/scrypt.rb +7 -6
- data/lib/authlogic/regex.rb +3 -2
- data/lib/authlogic/session/activation.rb +5 -3
- data/lib/authlogic/session/active_record_trickery.rb +23 -1
- data/lib/authlogic/session/callbacks.rb +8 -3
- data/lib/authlogic/session/cookies.rb +52 -17
- data/lib/authlogic/session/foundation.rb +1 -9
- data/lib/authlogic/session/magic_columns.rb +3 -3
- data/lib/authlogic/session/scopes.rb +11 -4
- data/lib/authlogic/session/session.rb +8 -8
- data/lib/authlogic/test_case.rb +7 -5
- data/lib/authlogic/test_case/mock_cookie_jar.rb +25 -0
- data/lib/authlogic/test_case/mock_request.rb +2 -2
- data/test/acts_as_authentic_test/logged_in_status_test.rb +3 -3
- data/test/acts_as_authentic_test/password_test.rb +16 -7
- data/test/crypto_provider_test/bcrypt_test.rb +1 -9
- data/test/fixtures/users.yml +13 -1
- data/test/gemfiles/Gemfile.rails-3.2.x +5 -0
- data/test/gemfiles/Gemfile.rails-4.0.x +5 -0
- data/test/gemfiles/Gemfile.rails-4.1.x +5 -0
- data/test/session_test/active_record_trickery_test.rb +29 -0
- data/test/session_test/cookies_test.rb +26 -1
- data/test/session_test/session_test.rb +7 -7
- data/test/test_helper.rb +3 -1
- metadata +59 -55
- data/lib/authlogic/controller_adapters/rack_adapter.rb +0 -63
@@ -52,7 +52,7 @@ module Authlogic
|
|
52
52
|
|
53
53
|
# Class level methods for the perishable token
|
54
54
|
module ClassMethods
|
55
|
-
# Use this
|
55
|
+
# Use this methdo to find a record with a perishable token. This method does 2 things for you:
|
56
56
|
#
|
57
57
|
# 1. It ignores blank tokens
|
58
58
|
# 2. It enforces the perishable_token_valid_for configuration option.
|
@@ -42,7 +42,7 @@ module Authlogic
|
|
42
42
|
options[:relationship_name] ||= options[:session_class].klass_name.underscore.pluralize
|
43
43
|
class_eval <<-"end_eval", __FILE__, __LINE__
|
44
44
|
def #{name}
|
45
|
-
find_options = #{options[:find_options].inspect} || #{options[:relationship_name]}.
|
45
|
+
find_options = #{options[:find_options].inspect} || #{options[:relationship_name]}.where(nil)
|
46
46
|
@#{name} ||= Authlogic::AuthenticatesMany::Association.new(#{options[:session_class]}, find_options, #{options[:scope_cookies] ? "self.class.model_name.underscore + '_' + self.send(self.class.primary_key).to_s" : "nil"})
|
47
47
|
end
|
48
48
|
end_eval
|
@@ -6,10 +6,14 @@ end
|
|
6
6
|
|
7
7
|
module Authlogic
|
8
8
|
module CryptoProviders
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
9
|
+
# The family of adaptive hash functions (BCrypt, SCrypt, PBKDF2)
|
10
|
+
# is the best choice for password storage today. They have the
|
11
|
+
# three properties of password hashing that are desirable. They
|
12
|
+
# are one-way, unique, and slow. While a salted SHA or MD5 hash is
|
13
|
+
# one-way and unique, preventing rainbow table attacks, they are
|
14
|
+
# still lightning fast and attacks on the stored passwords are
|
15
|
+
# much more effective. This benchmark demonstrates the effective
|
16
|
+
# slowdown that BCrypt provides:
|
13
17
|
#
|
14
18
|
# require "bcrypt"
|
15
19
|
# require "digest"
|
@@ -17,18 +21,20 @@ module Authlogic
|
|
17
21
|
#
|
18
22
|
# Benchmark.bm(18) do |x|
|
19
23
|
# x.report("BCrypt (cost = 10:") { 100.times { BCrypt::Password.create("mypass", :cost => 10) } }
|
20
|
-
# x.report("BCrypt (cost =
|
24
|
+
# x.report("BCrypt (cost = 2:") { 100.times { BCrypt::Password.create("mypass", :cost => 2) } }
|
21
25
|
# x.report("Sha512:") { 100.times { Digest::SHA512.hexdigest("mypass") } }
|
22
26
|
# x.report("Sha1:") { 100.times { Digest::SHA1.hexdigest("mypass") } }
|
23
27
|
# end
|
24
28
|
#
|
25
|
-
#
|
26
|
-
# BCrypt (cost = 10):
|
27
|
-
# BCrypt (cost =
|
28
|
-
# Sha512:
|
29
|
-
# Sha1:
|
29
|
+
# user system total real
|
30
|
+
# BCrypt (cost = 10): 10.780000 0.060000 10.840000 ( 11.100289)
|
31
|
+
# BCrypt (cost = 2): 0.180000 0.000000 0.180000 ( 0.181914)
|
32
|
+
# Sha512: 0.000000 0.000000 0.000000 ( 0.000829)
|
33
|
+
# Sha1: 0.000000 0.000000 0.000000 ( 0.000395)
|
30
34
|
#
|
31
|
-
# You can play around with the cost to get that perfect balance
|
35
|
+
# You can play around with the cost to get that perfect balance
|
36
|
+
# between performance and security. A default cost of 10 is the
|
37
|
+
# best place to start.
|
32
38
|
#
|
33
39
|
# Decided BCrypt is for you? Just install the bcrypt gem:
|
34
40
|
#
|
@@ -44,16 +50,11 @@ module Authlogic
|
|
44
50
|
class BCrypt
|
45
51
|
class << self
|
46
52
|
# This is the :cost option for the BCrpyt library. The higher the cost the more secure it is and the longer is take the generate a hash. By default this is 10.
|
47
|
-
# Set this to
|
53
|
+
# Set this to whatever you want, play around with it to get that perfect balance between security and performance.
|
48
54
|
def cost
|
49
55
|
@cost ||= 10
|
50
56
|
end
|
51
|
-
|
52
|
-
if val < ::BCrypt::Engine::MIN_COST
|
53
|
-
raise ArgumentError.new("Authlogic's bcrypt cost cannot be set below the engine's min cost (#{::BCrypt::Engine::MIN_COST})")
|
54
|
-
end
|
55
|
-
@cost = val
|
56
|
-
end
|
57
|
+
attr_writer :cost
|
57
58
|
|
58
59
|
# Creates a BCrypt hash for the password passed.
|
59
60
|
def encrypt(*tokens)
|
@@ -6,12 +6,13 @@ end
|
|
6
6
|
|
7
7
|
module Authlogic
|
8
8
|
module CryptoProviders
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
9
|
+
# SCrypt is the default provider for Authlogic. It is the only
|
10
|
+
# choice in the adaptive hash family that accounts for hardware
|
11
|
+
# based attacks by compensating with memory bound as well as cpu
|
12
|
+
# bound computational constraints. It offers the same guarantees
|
13
|
+
# as BCrypt in the way of one-way, unique and slow.
|
14
|
+
#
|
15
|
+
# Decided SCrypt is for you? Just install the scrypt gem:
|
15
16
|
#
|
16
17
|
# gem install scrypt
|
17
18
|
#
|
data/lib/authlogic/regex.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
#encoding: utf-8
|
1
2
|
module Authlogic
|
2
3
|
# This is a module the contains regular expressions used throughout Authlogic. The point of extracting
|
3
4
|
# them out into their own module is to make them easily available to you for other uses. Ex:
|
@@ -12,11 +13,11 @@ module Authlogic
|
|
12
13
|
@email_regex ||= begin
|
13
14
|
email_name_regex = '[A-Z0-9_\.%\+\-\']+'
|
14
15
|
domain_head_regex = '(?:[A-Z0-9\-]+\.)+'
|
15
|
-
domain_tld_regex = '(?:[A-Z]{2,4}|museum|travel)'
|
16
|
+
domain_tld_regex = '(?:[A-Z]{2,4}|museum|travel|онлайн)'
|
16
17
|
/\A#{email_name_regex}@#{domain_head_regex}#{domain_tld_regex}\z/i
|
17
18
|
end
|
18
19
|
end
|
19
|
-
|
20
|
+
|
20
21
|
# A simple regular expression that only allows for letters, numbers, spaces, and .-_@. Just a standard login / username
|
21
22
|
# regular expression.
|
22
23
|
def self.login
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'request_store'
|
2
|
+
|
1
3
|
module Authlogic
|
2
4
|
module Session
|
3
5
|
# Activating Authlogic requires that you pass it an Authlogic::ControllerAdapters::AbstractAdapter object, or a class that extends it.
|
@@ -32,12 +34,12 @@ module Authlogic
|
|
32
34
|
#
|
33
35
|
# Lastly, this is thread safe.
|
34
36
|
def controller=(value)
|
35
|
-
|
37
|
+
RequestStore.store[:authlogic_controller] = value
|
36
38
|
end
|
37
39
|
|
38
40
|
# The current controller object
|
39
41
|
def controller
|
40
|
-
|
42
|
+
RequestStore.store[:authlogic_controller]
|
41
43
|
end
|
42
44
|
end
|
43
45
|
|
@@ -55,4 +57,4 @@ module Authlogic
|
|
55
57
|
end
|
56
58
|
end
|
57
59
|
end
|
58
|
-
end
|
60
|
+
end
|
@@ -27,7 +27,17 @@ module Authlogic
|
|
27
27
|
def human_name(*args)
|
28
28
|
I18n.t("models.#{name.underscore}", {:count => 1, :default => name.humanize})
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
|
+
# For rails < 2.3, mispelled
|
32
|
+
def self_and_descendents_from_active_record
|
33
|
+
[self]
|
34
|
+
end
|
35
|
+
|
36
|
+
# For rails >= 2.3, mispelling fixed
|
37
|
+
def self_and_descendants_from_active_record
|
38
|
+
[self]
|
39
|
+
end
|
40
|
+
|
31
41
|
# For rails >= 3.0
|
32
42
|
def model_name
|
33
43
|
if defined?(::ActiveModel)
|
@@ -51,7 +61,19 @@ module Authlogic
|
|
51
61
|
def new_record?
|
52
62
|
new_session?
|
53
63
|
end
|
64
|
+
|
65
|
+
def persisted?
|
66
|
+
!(new_record? || destroyed?)
|
67
|
+
end
|
68
|
+
|
69
|
+
def destroyed?
|
70
|
+
record.nil?
|
71
|
+
end
|
54
72
|
|
73
|
+
def to_key
|
74
|
+
new_record? ? nil : record.to_key
|
75
|
+
end
|
76
|
+
|
55
77
|
# For rails >= 3.0
|
56
78
|
def to_model
|
57
79
|
self
|
@@ -63,8 +63,13 @@ module Authlogic
|
|
63
63
|
|
64
64
|
def self.included(base) #:nodoc:
|
65
65
|
base.send :include, ActiveSupport::Callbacks
|
66
|
-
|
67
|
-
|
66
|
+
if ActiveSupport::VERSION::STRING >= '4.1'
|
67
|
+
base.define_callbacks *METHODS + [{:terminator => ->(target, result){ result == false } }]
|
68
|
+
base.define_callbacks *['persist', {:terminator => ->(target, result){ result == true } }]
|
69
|
+
else
|
70
|
+
base.define_callbacks *METHODS + [{:terminator => 'result == false'}]
|
71
|
+
base.define_callbacks *['persist', {:terminator => 'result == true'}]
|
72
|
+
end
|
68
73
|
|
69
74
|
# If Rails 3, support the new callback syntax
|
70
75
|
if base.singleton_class.method_defined?(:set_callback)
|
@@ -93,4 +98,4 @@ module Authlogic
|
|
93
98
|
end
|
94
99
|
end
|
95
100
|
end
|
96
|
-
end
|
101
|
+
end
|
@@ -11,7 +11,7 @@ module Authlogic
|
|
11
11
|
after_destroy :destroy_cookie
|
12
12
|
end
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
# Configuration for the cookie feature set.
|
16
16
|
module Config
|
17
17
|
# The name of the cookie or the key in the cookies hash. Be sure and use a unique name. If you have multiple sessions and they use the same cookie it will cause problems.
|
@@ -19,7 +19,7 @@ module Authlogic
|
|
19
19
|
#
|
20
20
|
# session = UserSession.new
|
21
21
|
# session.cookie_key => "user_credentials"
|
22
|
-
#
|
22
|
+
#
|
23
23
|
# session = UserSession.new(:super_high_secret)
|
24
24
|
# session.cookie_key => "super_high_secret_user_credentials"
|
25
25
|
#
|
@@ -29,7 +29,7 @@ module Authlogic
|
|
29
29
|
rw_config(:cookie_key, value, "#{klass_name.underscore}_credentials")
|
30
30
|
end
|
31
31
|
alias_method :cookie_key=, :cookie_key
|
32
|
-
|
32
|
+
|
33
33
|
# If sessions should be remembered by default or not.
|
34
34
|
#
|
35
35
|
# * <tt>Default:</tt> false
|
@@ -38,7 +38,7 @@ module Authlogic
|
|
38
38
|
rw_config(:remember_me, value, false)
|
39
39
|
end
|
40
40
|
alias_method :remember_me=, :remember_me
|
41
|
-
|
41
|
+
|
42
42
|
# The length of time until the cookie expires.
|
43
43
|
#
|
44
44
|
# * <tt>Default:</tt> 3.months
|
@@ -65,8 +65,17 @@ module Authlogic
|
|
65
65
|
rw_config(:httponly, value, false)
|
66
66
|
end
|
67
67
|
alias_method :httponly=, :httponly
|
68
|
+
|
69
|
+
# Should the cookie be signed? If the controller adapter supports it, this is a measure against cookie tampering.
|
70
|
+
def sign_cookie(value = nil)
|
71
|
+
if value && !controller.cookies.respond_to?(:signed)
|
72
|
+
raise "Signed cookies not supported with #{controller.class}!"
|
73
|
+
end
|
74
|
+
rw_config(:sign_cookie, value, false)
|
75
|
+
end
|
76
|
+
alias_method :sign_cookie=, :sign_cookie
|
68
77
|
end
|
69
|
-
|
78
|
+
|
70
79
|
# The methods available for an Authlogic::Session::Base object that make up the cookie feature set.
|
71
80
|
module InstanceMethods
|
72
81
|
# Allows you to set the remember_me option when passing credentials.
|
@@ -81,29 +90,29 @@ module Authlogic
|
|
81
90
|
self.remember_me = r if !r.nil?
|
82
91
|
end
|
83
92
|
end
|
84
|
-
|
93
|
+
|
85
94
|
# Is the cookie going to expire after the session is over, or will it stick around?
|
86
95
|
def remember_me
|
87
96
|
return @remember_me if defined?(@remember_me)
|
88
97
|
@remember_me = self.class.remember_me
|
89
98
|
end
|
90
|
-
|
99
|
+
|
91
100
|
# Accepts a boolean as a flag to remember the session or not. Basically to expire the cookie at the end of the session or keep it for "remember_me_until".
|
92
101
|
def remember_me=(value)
|
93
102
|
@remember_me = value
|
94
103
|
end
|
95
|
-
|
104
|
+
|
96
105
|
# See remember_me
|
97
106
|
def remember_me?
|
98
107
|
remember_me == true || remember_me == "true" || remember_me == "1"
|
99
108
|
end
|
100
|
-
|
109
|
+
|
101
110
|
# How long to remember the user if remember_me is true. This is based on the class level configuration: remember_me_for
|
102
111
|
def remember_me_for
|
103
112
|
return unless remember_me?
|
104
113
|
self.class.remember_me_for
|
105
114
|
end
|
106
|
-
|
115
|
+
|
107
116
|
# When to expire the cookie. See remember_me_for configuration option to change this.
|
108
117
|
def remember_me_until
|
109
118
|
return unless remember_me?
|
@@ -148,15 +157,35 @@ module Authlogic
|
|
148
157
|
httponly == true || httponly == "true" || httponly == "1"
|
149
158
|
end
|
150
159
|
|
160
|
+
# If the cookie should be signed
|
161
|
+
def sign_cookie
|
162
|
+
return @sign_cookie if defined?(@sign_cookie)
|
163
|
+
@sign_cookie = self.class.sign_cookie
|
164
|
+
end
|
165
|
+
|
166
|
+
# Accepts a boolean as to whether the cookie should be signed. If true the cookie will be saved and verified using a signature.
|
167
|
+
def sign_cookie=(value)
|
168
|
+
@sign_cookie = value
|
169
|
+
end
|
170
|
+
|
171
|
+
# See sign_cookie
|
172
|
+
def sign_cookie?
|
173
|
+
sign_cookie == true || sign_cookie == "true" || sign_cookie == "1"
|
174
|
+
end
|
175
|
+
|
151
176
|
private
|
152
177
|
def cookie_key
|
153
178
|
build_key(self.class.cookie_key)
|
154
179
|
end
|
155
|
-
|
180
|
+
|
156
181
|
def cookie_credentials
|
157
|
-
|
182
|
+
if self.class.sign_cookie
|
183
|
+
controller.cookies.signed[cookie_key] && controller.cookies.signed[cookie_key].split("::")
|
184
|
+
else
|
185
|
+
controller.cookies[cookie_key] && controller.cookies[cookie_key].split("::")
|
186
|
+
end
|
158
187
|
end
|
159
|
-
|
188
|
+
|
160
189
|
# Tries to validate the session from information in the cookie
|
161
190
|
def persist_by_cookie
|
162
191
|
persistence_token, record_id = cookie_credentials
|
@@ -168,18 +197,24 @@ module Authlogic
|
|
168
197
|
false
|
169
198
|
end
|
170
199
|
end
|
171
|
-
|
200
|
+
|
172
201
|
def save_cookie
|
173
|
-
remember_me_until_value = "::#{remember_me_until}" if remember_me?
|
174
|
-
|
202
|
+
remember_me_until_value = "::#{remember_me_until.iso8601}" if remember_me?
|
203
|
+
cookie = {
|
175
204
|
:value => "#{record.persistence_token}::#{record.send(record.class.primary_key)}#{remember_me_until_value}",
|
176
205
|
:expires => remember_me_until,
|
177
206
|
:secure => secure,
|
178
207
|
:httponly => httponly,
|
179
208
|
:domain => controller.cookie_domain
|
180
209
|
}
|
210
|
+
|
211
|
+
if sign_cookie?
|
212
|
+
controller.cookies.signed[cookie_key] = cookie
|
213
|
+
else
|
214
|
+
controller.cookies[cookie_key] = cookie
|
215
|
+
end
|
181
216
|
end
|
182
|
-
|
217
|
+
|
183
218
|
def destroy_cookie
|
184
219
|
controller.cookies.delete cookie_key, :domain => controller.cookie_domain
|
185
220
|
end
|
@@ -58,15 +58,7 @@ module Authlogic
|
|
58
58
|
def inspect
|
59
59
|
"#<#{self.class.name}: #{credentials.blank? ? "no credentials provided" : credentials.inspect}>"
|
60
60
|
end
|
61
|
-
|
62
|
-
def persisted?
|
63
|
-
!(new_record? || destroyed?)
|
64
|
-
end
|
65
|
-
|
66
|
-
def to_key
|
67
|
-
new_record? ? nil : [ self.send(self.class.primary_key) ]
|
68
|
-
end
|
69
|
-
|
61
|
+
|
70
62
|
private
|
71
63
|
def build_key(last_part)
|
72
64
|
last_part
|
@@ -8,7 +8,7 @@ module Authlogic
|
|
8
8
|
# last_request_at Updates every time the user logs in, either by explicitly logging in, or logging in by cookie, session, or http auth
|
9
9
|
# current_login_at Updates with the current time when an explicit login is made.
|
10
10
|
# last_login_at Updates with the value of current_login_at before it is reset.
|
11
|
-
# current_login_ip Updates with the request
|
11
|
+
# current_login_ip Updates with the request remote_ip when an explicit login is made.
|
12
12
|
# last_login_ip Updates with the value of current_login_ip before it is reset.
|
13
13
|
module MagicColumns
|
14
14
|
def self.included(klass)
|
@@ -58,7 +58,7 @@ module Authlogic
|
|
58
58
|
|
59
59
|
if record.respond_to?(:current_login_ip)
|
60
60
|
record.last_login_ip = record.current_login_ip if record.respond_to?(:last_login_ip)
|
61
|
-
record.current_login_ip = controller.request.
|
61
|
+
record.current_login_ip = controller.request.remote_ip
|
62
62
|
end
|
63
63
|
end
|
64
64
|
|
@@ -72,7 +72,7 @@ module Authlogic
|
|
72
72
|
# So you can do something like this in your controller:
|
73
73
|
#
|
74
74
|
# def last_request_update_allowed?
|
75
|
-
# action_name
|
75
|
+
# action_name != "update_session_time_left"
|
76
76
|
# end
|
77
77
|
#
|
78
78
|
# You can do whatever you want with that method.
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'request_store'
|
2
|
+
|
1
3
|
module Authlogic
|
2
4
|
module Session
|
3
5
|
# Authentication can be scoped, and it's easy, you just need to define how you want to scope everything. This should help you:
|
@@ -17,7 +19,7 @@ module Authlogic
|
|
17
19
|
module ClassMethods
|
18
20
|
# The current scope set, should be used in the block passed to with_scope.
|
19
21
|
def scope
|
20
|
-
|
22
|
+
RequestStore.store[:authlogic_scope]
|
21
23
|
end
|
22
24
|
|
23
25
|
# What with_scopes focuses on is scoping the query when finding the object and the name of the cookie / session. It works very similar to
|
@@ -68,7 +70,7 @@ module Authlogic
|
|
68
70
|
|
69
71
|
private
|
70
72
|
def scope=(value)
|
71
|
-
|
73
|
+
RequestStore.store[:authlogic_scope] = value
|
72
74
|
end
|
73
75
|
end
|
74
76
|
|
@@ -91,11 +93,16 @@ module Authlogic
|
|
91
93
|
end
|
92
94
|
|
93
95
|
def search_for_record(*args)
|
94
|
-
|
96
|
+
session_scope = if scope[:find_options].is_a?(ActiveRecord::Relation)
|
97
|
+
scope[:find_options]
|
98
|
+
else
|
99
|
+
klass.send(:where, scope[:find_options] && scope[:find_options][:conditions] || {})
|
100
|
+
end
|
101
|
+
session_scope.scoping do
|
95
102
|
klass.send(*args)
|
96
103
|
end
|
97
104
|
end
|
98
105
|
end
|
99
106
|
end
|
100
107
|
end
|
101
|
-
end
|
108
|
+
end
|