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.
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