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