authlogic 3.3.0 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -1
  3. data/.travis.yml +27 -0
  4. data/CONTRIBUTING.md +10 -0
  5. data/Gemfile.lock +46 -28
  6. data/History +10 -0
  7. data/README.rdoc +2 -0
  8. data/Rakefile +0 -13
  9. data/authlogic.gemspec +8 -7
  10. data/lib/authlogic/acts_as_authentic/email.rb +1 -1
  11. data/lib/authlogic/acts_as_authentic/login.rb +12 -13
  12. data/lib/authlogic/acts_as_authentic/password.rb +47 -47
  13. data/lib/authlogic/acts_as_authentic/perishable_token.rb +1 -1
  14. data/lib/authlogic/acts_as_authentic/persistence_token.rb +1 -1
  15. data/lib/authlogic/authenticates_many/base.rb +1 -1
  16. data/lib/authlogic/controller_adapters/sinatra_adapter.rb +1 -1
  17. data/lib/authlogic/crypto_providers/bcrypt.rb +19 -18
  18. data/lib/authlogic/crypto_providers/scrypt.rb +7 -6
  19. data/lib/authlogic/regex.rb +3 -2
  20. data/lib/authlogic/session/activation.rb +5 -3
  21. data/lib/authlogic/session/active_record_trickery.rb +23 -1
  22. data/lib/authlogic/session/callbacks.rb +8 -3
  23. data/lib/authlogic/session/cookies.rb +52 -17
  24. data/lib/authlogic/session/foundation.rb +1 -9
  25. data/lib/authlogic/session/magic_columns.rb +3 -3
  26. data/lib/authlogic/session/scopes.rb +11 -4
  27. data/lib/authlogic/session/session.rb +8 -8
  28. data/lib/authlogic/test_case.rb +7 -5
  29. data/lib/authlogic/test_case/mock_cookie_jar.rb +25 -0
  30. data/lib/authlogic/test_case/mock_request.rb +2 -2
  31. data/test/acts_as_authentic_test/logged_in_status_test.rb +3 -3
  32. data/test/acts_as_authentic_test/password_test.rb +16 -7
  33. data/test/crypto_provider_test/bcrypt_test.rb +1 -9
  34. data/test/fixtures/users.yml +13 -1
  35. data/test/gemfiles/Gemfile.rails-3.2.x +5 -0
  36. data/test/gemfiles/Gemfile.rails-4.0.x +5 -0
  37. data/test/gemfiles/Gemfile.rails-4.1.x +5 -0
  38. data/test/session_test/active_record_trickery_test.rb +29 -0
  39. data/test/session_test/cookies_test.rb +26 -1
  40. data/test/session_test/session_test.rb +7 -7
  41. data/test/test_helper.rb +3 -1
  42. metadata +59 -55
  43. 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 method to find a record with a perishable token. This method does 2 things for you:
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.
@@ -36,7 +36,7 @@ module Authlogic
36
36
  records = nil
37
37
  i = 0
38
38
  begin
39
- records = find(:all, :limit => 50, :offset => i)
39
+ records = limit(50).offset(i)
40
40
  records.each { |record| record.forget! }
41
41
  i += 50
42
42
  end while !records.blank?
@@ -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]}.scoped
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
@@ -11,7 +11,7 @@ module Authlogic
11
11
  end
12
12
 
13
13
  def delete(key, options = {})
14
- @request.cookies.delete(key)
14
+ @response.delete_cookie(key, options)
15
15
  end
16
16
 
17
17
  def []=(key, options)
@@ -6,10 +6,14 @@ end
6
6
 
7
7
  module Authlogic
8
8
  module CryptoProviders
9
- # For most apps Sha512 is plenty secure, but if you are building an app that stores nuclear launch codes you might want to consier BCrypt. This is an extremely
10
- # secure hashing algorithm, mainly because it is slow. A brute force attack on a BCrypt encrypted password would take much longer than a brute force attack on a
11
- # password encrypted with a Sha algorithm. Keep in mind you are sacrificing performance by using this, generating a password takes exponentially longer than any
12
- # of the Sha algorithms. I did some benchmarking to save you some time with your decision:
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 = 4:") { 100.times { BCrypt::Password.create("mypass", :cost => 4) } }
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
- # user system total real
26
- # BCrypt (cost = 10): 37.360000 0.020000 37.380000 ( 37.558943)
27
- # BCrypt (cost = 4): 0.680000 0.000000 0.680000 ( 0.677460)
28
- # Sha512: 0.000000 0.000000 0.000000 ( 0.000672)
29
- # Sha1: 0.000000 0.000000 0.000000 ( 0.000454)
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 between performance and security.
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 any value >= the engine's minimum (currently 4), play around with it to get that perfect balance between security and performance.
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
- def cost=(val)
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
- # If you want a stronger hashing algorithm, but would prefer not to use BCrypt, SCrypt is another option.
10
- # SCrypt is newer and less popular (and so less-tested), but it's designed specifically to avoid a theoretical
11
- # hardware attack against BCrypt. Just as with BCrypt, you are sacrificing performance relative to SHA2 algorithms,
12
- # but the increased security may well be worth it. (That performance sacrifice is the exact reason it's much, much
13
- # harder for an attacker to brute-force your paswords).
14
- # Decided SCrypt is for you? Just install the bcrypt gem:
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
  #
@@ -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
- Thread.current[:authlogic_controller] = value
37
+ RequestStore.store[:authlogic_controller] = value
36
38
  end
37
39
 
38
40
  # The current controller object
39
41
  def controller
40
- Thread.current[:authlogic_controller]
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
- base.define_callbacks *METHODS + [{:terminator => 'result == false'}]
67
- base.define_callbacks *['persist', {:terminator => 'result == true'}]
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
- controller.cookies[cookie_key] && controller.cookies[cookie_key].split("::")
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
- controller.cookies[cookie_key] = {
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 ip when an explicit login is made.
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.ip
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 =! "update_session_time_left"
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
- Thread.current[:authlogic_scope]
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
- Thread.current[:authlogic_scope] = value
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
- klass.send(:with_scope, :find => (scope[:find_options] || {})) do
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