authlogic 2.0.0 → 2.1.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.
- data/.gitignore +9 -0
- data/CHANGELOG.rdoc +107 -2
- data/{MIT-LICENSE → LICENSE} +1 -1
- data/README.rdoc +136 -159
- data/Rakefile +47 -18
- data/VERSION.yml +4 -0
- data/authlogic.gemspec +205 -0
- data/init.rb +1 -1
- data/lib/authlogic/acts_as_authentic/base.rb +33 -12
- data/lib/authlogic/acts_as_authentic/email.rb +47 -14
- data/lib/authlogic/acts_as_authentic/logged_in_status.rb +20 -14
- data/lib/authlogic/acts_as_authentic/login.rb +84 -8
- data/lib/authlogic/acts_as_authentic/password.rb +216 -87
- data/lib/authlogic/acts_as_authentic/perishable_token.rb +9 -4
- data/lib/authlogic/acts_as_authentic/persistence_token.rb +4 -2
- data/lib/authlogic/acts_as_authentic/restful_authentication.rb +5 -4
- data/lib/authlogic/acts_as_authentic/session_maintenance.rb +17 -5
- data/lib/authlogic/acts_as_authentic/single_access_token.rb +10 -3
- data/lib/authlogic/acts_as_authentic/validations_scope.rb +2 -2
- data/lib/authlogic/controller_adapters/abstract_adapter.rb +13 -1
- data/lib/authlogic/controller_adapters/rails_adapter.rb +1 -1
- data/lib/authlogic/crypto_providers/aes256.rb +2 -2
- data/lib/authlogic/crypto_providers/bcrypt.rb +3 -1
- data/lib/authlogic/crypto_providers/sha1.rb +2 -2
- data/lib/authlogic/i18n.rb +10 -1
- data/lib/authlogic/regex.rb +25 -0
- data/lib/authlogic/session/activation.rb +2 -0
- data/lib/authlogic/session/active_record_trickery.rb +19 -3
- data/lib/authlogic/session/brute_force_protection.rb +26 -11
- data/lib/authlogic/session/callbacks.rb +16 -6
- data/lib/authlogic/session/cookies.rb +15 -11
- data/lib/authlogic/session/existence.rb +4 -2
- data/lib/authlogic/session/foundation.rb +1 -1
- data/lib/authlogic/session/http_auth.rb +46 -11
- data/lib/authlogic/session/magic_columns.rb +25 -6
- data/lib/authlogic/session/magic_states.rb +4 -4
- data/lib/authlogic/session/params.rb +14 -9
- data/lib/authlogic/session/password.rb +86 -24
- data/lib/authlogic/session/priority_record.rb +1 -1
- data/lib/authlogic/session/session.rb +2 -2
- data/lib/authlogic/session/timeout.rb +1 -1
- data/lib/authlogic/session/validation.rb +9 -5
- data/lib/authlogic/test_case/mock_controller.rb +45 -0
- data/lib/authlogic/test_case/mock_cookie_jar.rb +14 -0
- data/lib/authlogic/test_case/mock_logger.rb +10 -0
- data/lib/authlogic/test_case/mock_request.rb +19 -0
- data/lib/authlogic/test_case/rails_request_adapter.rb +30 -0
- data/lib/authlogic/test_case.rb +114 -0
- data/lib/authlogic.rb +1 -1
- data/rails/init.rb +1 -0
- data/shoulda_macros/authlogic.rb +2 -1
- data/test/acts_as_authentic_test/base_test.rb +6 -0
- data/test/acts_as_authentic_test/email_test.rb +25 -7
- data/test/acts_as_authentic_test/login_test.rb +37 -7
- data/test/acts_as_authentic_test/magic_columns_test.rb +4 -4
- data/test/acts_as_authentic_test/password_test.rb +64 -40
- data/test/acts_as_authentic_test/perishable_token_test.rb +36 -2
- data/test/acts_as_authentic_test/restful_authentication_test.rb +40 -0
- data/test/acts_as_authentic_test/session_maintenance_test.rb +34 -18
- data/test/acts_as_authentic_test/single_access_test.rb +6 -1
- data/test/libs/affiliate.rb +7 -0
- data/test/libs/ldaper.rb +3 -0
- data/test/random_test.rb +2 -2
- data/test/session_test/activation_test.rb +1 -1
- data/test/session_test/active_record_trickery_test.rb +2 -1
- data/test/session_test/brute_force_protection_test.rb +34 -9
- data/test/session_test/cookies_test.rb +3 -3
- data/test/session_test/http_auth_test.rb +20 -8
- data/test/session_test/magic_columns_test.rb +7 -4
- data/test/session_test/magic_states_test.rb +3 -3
- data/test/session_test/params_test.rb +4 -4
- data/test/session_test/password_test.rb +8 -0
- data/test/session_test/session_test.rb +9 -9
- data/test/session_test/timeout_test.rb +10 -1
- data/test/test_helper.rb +46 -24
- metadata +38 -27
- data/Manifest.txt +0 -111
- data/lib/authlogic/testing/test_unit_helpers.rb +0 -39
- data/lib/authlogic/version.rb +0 -56
- data/test/libs/mock_controller.rb +0 -35
- data/test/libs/mock_cookie_jar.rb +0 -10
- data/test/libs/mock_request.rb +0 -5
|
@@ -29,40 +29,72 @@ module Authlogic
|
|
|
29
29
|
# end
|
|
30
30
|
# end
|
|
31
31
|
#
|
|
32
|
-
#
|
|
32
|
+
# Now just specify the name of this method for this configuration option and you are all set. You can do anything you want here. Maybe you allow users to have multiple logins
|
|
33
|
+
# and you want to search a has_many relationship, etc. The sky is the limit.
|
|
34
|
+
#
|
|
35
|
+
# * <tt>Default:</tt> "find_by_smart_case_login_field"
|
|
33
36
|
# * <tt>Accepts:</tt> Symbol or String
|
|
34
37
|
def find_by_login_method(value = nil)
|
|
35
|
-
|
|
38
|
+
rw_config(:find_by_login_method, value, "find_by_smart_case_login_field")
|
|
36
39
|
end
|
|
37
40
|
alias_method :find_by_login_method=, :find_by_login_method
|
|
38
41
|
|
|
42
|
+
# The text used to identify credentials (username/password) combination when a bad login attempt occurs.
|
|
43
|
+
# When you show error messages for a bad login, it's considered good security practice to hide which field
|
|
44
|
+
# the user has entered incorrectly (the login field or the password field). For a full explanation, see
|
|
45
|
+
# http://www.gnucitizen.org/blog/username-enumeration-vulnerabilities/
|
|
46
|
+
#
|
|
47
|
+
# Example of use:
|
|
48
|
+
#
|
|
49
|
+
# class UserSession < Authlogic::Session::Base
|
|
50
|
+
# generalize_credentials_error_messages true
|
|
51
|
+
# end
|
|
52
|
+
#
|
|
53
|
+
# This would make the error message for bad logins and bad passwords look identical:
|
|
54
|
+
#
|
|
55
|
+
# Login/Password combination is not valid
|
|
56
|
+
#
|
|
57
|
+
# The downside to enabling this is that is can be too vague for a user that has a hard time remembering
|
|
58
|
+
# their username and password combinations. It also disables the ability to to highlight the field
|
|
59
|
+
# with the error when you use form_for.
|
|
60
|
+
#
|
|
61
|
+
# If you are developing an app where security is an extreme priority (such as a financial application),
|
|
62
|
+
# then you should enable this. Otherwise, leaving this off is fine.
|
|
63
|
+
#
|
|
64
|
+
# * <tt>Default</tt> false
|
|
65
|
+
# * <tt>Accepts:</tt> Boolean
|
|
66
|
+
def generalize_credentials_error_messages(value = nil)
|
|
67
|
+
rw_config(:generalize_credentials_error_messages, value, false)
|
|
68
|
+
end
|
|
69
|
+
alias_method :generalize_credentials_error_messages=, :generalize_credentials_error_messages
|
|
70
|
+
|
|
39
71
|
# The name of the method you want Authlogic to create for storing the login / username. Keep in mind this is just for your
|
|
40
72
|
# Authlogic::Session, if you want it can be something completely different than the field in your model. So if you wanted people to
|
|
41
73
|
# login with a field called "login" and then find users by email this is compeltely doable. See the find_by_login_method configuration
|
|
42
74
|
# option for more details.
|
|
43
75
|
#
|
|
44
|
-
# * <tt>Default:</tt>
|
|
76
|
+
# * <tt>Default:</tt> klass.login_field || klass.email_field
|
|
45
77
|
# * <tt>Accepts:</tt> Symbol or String
|
|
46
78
|
def login_field(value = nil)
|
|
47
|
-
|
|
79
|
+
rw_config(:login_field, value, klass.login_field || klass.email_field)
|
|
48
80
|
end
|
|
49
81
|
alias_method :login_field=, :login_field
|
|
50
82
|
|
|
51
|
-
# Works exactly like login_field, but for the password instead.
|
|
83
|
+
# Works exactly like login_field, but for the password instead. Returns :password if a login_field exists.
|
|
52
84
|
#
|
|
53
85
|
# * <tt>Default:</tt> :password
|
|
54
86
|
# * <tt>Accepts:</tt> Symbol or String
|
|
55
87
|
def password_field(value = nil)
|
|
56
|
-
|
|
88
|
+
rw_config(:password_field, value, login_field && :password)
|
|
57
89
|
end
|
|
58
90
|
alias_method :password_field=, :password_field
|
|
59
91
|
|
|
60
92
|
# The name of the method in your model used to verify the password. This should be an instance method. It should also be prepared to accept a raw password and a crytped password.
|
|
61
93
|
#
|
|
62
|
-
# * <tt>Default:</tt> "
|
|
94
|
+
# * <tt>Default:</tt> "valid_password?"
|
|
63
95
|
# * <tt>Accepts:</tt> Symbol or String
|
|
64
96
|
def verify_password_method(value = nil)
|
|
65
|
-
|
|
97
|
+
rw_config(:verify_password_method, value, "valid_password?")
|
|
66
98
|
end
|
|
67
99
|
alias_method :verify_password_method=, :verify_password_method
|
|
68
100
|
end
|
|
@@ -71,18 +103,23 @@ module Authlogic
|
|
|
71
103
|
module InstanceMethods
|
|
72
104
|
def initialize(*args)
|
|
73
105
|
if !self.class.configured_password_methods
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
106
|
+
if login_field
|
|
107
|
+
self.class.send(:attr_writer, login_field) if !respond_to?("#{login_field}=")
|
|
108
|
+
self.class.send(:attr_reader, login_field) if !respond_to?(login_field)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
if password_field
|
|
112
|
+
self.class.send(:attr_writer, password_field) if !respond_to?("#{password_field}=")
|
|
113
|
+
self.class.send(:define_method, password_field) {} if !respond_to?(password_field)
|
|
78
114
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
115
|
+
self.class.class_eval <<-"end_eval", __FILE__, __LINE__
|
|
116
|
+
private
|
|
117
|
+
# The password should not be accessible publicly. This way forms using form_for don't fill the password with the attempted password. To prevent this we just create this method that is private.
|
|
118
|
+
def protected_#{password_field}
|
|
119
|
+
@#{password_field}
|
|
120
|
+
end
|
|
121
|
+
end_eval
|
|
122
|
+
end
|
|
86
123
|
|
|
87
124
|
self.class.configured_password_methods = true
|
|
88
125
|
end
|
|
@@ -90,6 +127,7 @@ module Authlogic
|
|
|
90
127
|
super
|
|
91
128
|
end
|
|
92
129
|
|
|
130
|
+
# Returns the login_field / password_field credentials combination in hash form.
|
|
93
131
|
def credentials
|
|
94
132
|
if authenticating_with_password?
|
|
95
133
|
details = {}
|
|
@@ -101,6 +139,7 @@ module Authlogic
|
|
|
101
139
|
end
|
|
102
140
|
end
|
|
103
141
|
|
|
142
|
+
# Accepts the login_field / password_field credentials combination in hash form.
|
|
104
143
|
def credentials=(value)
|
|
105
144
|
super
|
|
106
145
|
values = value.is_a?(Array) ? value : [value]
|
|
@@ -112,29 +151,44 @@ module Authlogic
|
|
|
112
151
|
end
|
|
113
152
|
end
|
|
114
153
|
|
|
154
|
+
def invalid_password?
|
|
155
|
+
invalid_password == true
|
|
156
|
+
end
|
|
157
|
+
|
|
115
158
|
private
|
|
116
159
|
def authenticating_with_password?
|
|
117
|
-
!send(login_field).nil? || !send("protected_#{password_field}").nil?
|
|
160
|
+
login_field && (!send(login_field).nil? || !send("protected_#{password_field}").nil?)
|
|
118
161
|
end
|
|
119
162
|
|
|
120
163
|
def validate_by_password
|
|
121
|
-
|
|
122
|
-
|
|
164
|
+
self.invalid_password = false
|
|
165
|
+
|
|
166
|
+
errors.add(login_field, I18n.t('error_messages.login_blank', :default => "cannot be blank")) if send(login_field).blank?
|
|
167
|
+
errors.add(password_field, I18n.t('error_messages.password_blank', :default => "cannot be blank")) if send("protected_#{password_field}").blank?
|
|
123
168
|
return if errors.count > 0
|
|
124
169
|
|
|
125
170
|
self.attempted_record = search_for_record(find_by_login_method, send(login_field))
|
|
126
171
|
|
|
127
172
|
if attempted_record.blank?
|
|
128
|
-
errors.add(login_field, I18n.t('error_messages.login_not_found', :default => "
|
|
173
|
+
generalize_credentials_error_messages? ? add_general_credentials_error : errors.add(login_field, I18n.t('error_messages.login_not_found', :default => "is not valid"))
|
|
129
174
|
return
|
|
130
175
|
end
|
|
131
176
|
|
|
132
177
|
if !attempted_record.send(verify_password_method, send("protected_#{password_field}"))
|
|
133
|
-
|
|
178
|
+
self.invalid_password = true
|
|
179
|
+
generalize_credentials_error_messages? ? add_general_credentials_error : errors.add(password_field, I18n.t('error_messages.password_invalid', :default => "is not valid"))
|
|
134
180
|
return
|
|
135
181
|
end
|
|
136
182
|
end
|
|
137
183
|
|
|
184
|
+
def invalid_password
|
|
185
|
+
@invalid_password
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def invalid_password=(value)
|
|
189
|
+
@invalid_password = value
|
|
190
|
+
end
|
|
191
|
+
|
|
138
192
|
def find_by_login_method
|
|
139
193
|
self.class.find_by_login_method
|
|
140
194
|
end
|
|
@@ -143,6 +197,14 @@ module Authlogic
|
|
|
143
197
|
self.class.login_field
|
|
144
198
|
end
|
|
145
199
|
|
|
200
|
+
def add_general_credentials_error
|
|
201
|
+
errors.add(:base, I18n.t('error_messages.general_credentials_error', :default => "#{login_field.to_s.humanize}/Password combination is not valid"))
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def generalize_credentials_error_messages?
|
|
205
|
+
self.class.generalize_credentials_error_messages == true
|
|
206
|
+
end
|
|
207
|
+
|
|
146
208
|
def password_field
|
|
147
209
|
self.class.password_field
|
|
148
210
|
end
|
|
@@ -16,7 +16,7 @@ module Authlogic
|
|
|
16
16
|
def credentials=(value)
|
|
17
17
|
super
|
|
18
18
|
values = value.is_a?(Array) ? value : [value]
|
|
19
|
-
self.priority_record = values
|
|
19
|
+
self.priority_record = values[1] if values[1].class < ::ActiveRecord::Base
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
private
|
|
@@ -20,7 +20,7 @@ module Authlogic
|
|
|
20
20
|
# * <tt>Default:</tt> cookie_key
|
|
21
21
|
# * <tt>Accepts:</tt> Symbol or String
|
|
22
22
|
def session_key(value = nil)
|
|
23
|
-
|
|
23
|
+
rw_config(:session_key, value, cookie_key)
|
|
24
24
|
end
|
|
25
25
|
alias_method :session_key=, :session_key
|
|
26
26
|
end
|
|
@@ -49,7 +49,7 @@ module Authlogic
|
|
|
49
49
|
def session_key
|
|
50
50
|
build_key(self.class.session_key)
|
|
51
51
|
end
|
|
52
|
-
|
|
52
|
+
|
|
53
53
|
def update_session
|
|
54
54
|
controller.session[session_key] = record && record.persistence_token
|
|
55
55
|
controller.session["#{session_key}_#{klass.primary_key}"] = record && record.send(record.class.primary_key)
|
|
@@ -48,7 +48,7 @@ module Authlogic
|
|
|
48
48
|
# * <tt>Default:</tt> false
|
|
49
49
|
# * <tt>Accepts:</tt> Boolean
|
|
50
50
|
def logout_on_timeout(value = nil)
|
|
51
|
-
|
|
51
|
+
rw_config(:logout_on_timeout, value, false)
|
|
52
52
|
end
|
|
53
53
|
alias_method :logout_on_timeout=, :logout_on_timeout
|
|
54
54
|
end
|
|
@@ -10,10 +10,14 @@ module Authlogic
|
|
|
10
10
|
# private
|
|
11
11
|
# def check_if_awesome
|
|
12
12
|
# errors.add(:login, "must contain awesome") if login && !login.include?("awesome")
|
|
13
|
-
# errors.
|
|
13
|
+
# errors.add(:base, "You must be awesome to log in") unless attempted_record.awesome?
|
|
14
14
|
# end
|
|
15
15
|
# end
|
|
16
16
|
class Errors < ::ActiveRecord::Errors
|
|
17
|
+
def [](key)
|
|
18
|
+
value = super
|
|
19
|
+
value.is_a?(Array) ? value : [value].compact
|
|
20
|
+
end
|
|
17
21
|
end
|
|
18
22
|
|
|
19
23
|
# You should use this as a place holder for any records that you find during validation. The main reason for this is to
|
|
@@ -39,7 +43,7 @@ module Authlogic
|
|
|
39
43
|
# private
|
|
40
44
|
# def check_if_awesome
|
|
41
45
|
# errors.add(:login, "must contain awesome") if login && !login.include?("awesome")
|
|
42
|
-
# errors.
|
|
46
|
+
# errors.add(:base, "You must be awesome to log in") unless attempted_record.awesome?
|
|
43
47
|
# end
|
|
44
48
|
# end
|
|
45
49
|
def errors
|
|
@@ -58,18 +62,18 @@ module Authlogic
|
|
|
58
62
|
validate
|
|
59
63
|
ensure_authentication_attempted
|
|
60
64
|
|
|
61
|
-
if errors.
|
|
65
|
+
if errors.size == 0
|
|
62
66
|
new_session? ? after_validation_on_create : after_validation_on_update
|
|
63
67
|
after_validation
|
|
64
68
|
end
|
|
65
69
|
|
|
66
70
|
save_record(attempted_record)
|
|
67
|
-
errors.
|
|
71
|
+
errors.size == 0
|
|
68
72
|
end
|
|
69
73
|
|
|
70
74
|
private
|
|
71
75
|
def ensure_authentication_attempted
|
|
72
|
-
errors.
|
|
76
|
+
errors.add(:base, I18n.t('error_messages.no_authentication_details', :default => "You did not provide any details for authentication.")) if errors.empty? && attempted_record.nil?
|
|
73
77
|
end
|
|
74
78
|
end
|
|
75
79
|
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module Authlogic
|
|
2
|
+
module TestCase
|
|
3
|
+
# Basically acts like a controller but doesn't do anything. Authlogic can interact with this, do it's thing and then you
|
|
4
|
+
# can look at the controller object to see if anything changed.
|
|
5
|
+
class MockController < ControllerAdapters::AbstractAdapter
|
|
6
|
+
attr_accessor :http_user, :http_password
|
|
7
|
+
attr_writer :request_content_type
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def authenticate_with_http_basic(&block)
|
|
13
|
+
yield http_user, http_password
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def cookies
|
|
17
|
+
@cookies ||= MockCookieJar.new
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def cookie_domain
|
|
21
|
+
nil
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def logger
|
|
25
|
+
@logger ||= MockLogger.new
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def params
|
|
29
|
+
@params ||= {}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def request
|
|
33
|
+
@request ||= MockRequest.new(controller)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def request_content_type
|
|
37
|
+
@request_content_type ||= "text/html"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def session
|
|
41
|
+
@session ||= {}
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Authlogic
|
|
2
|
+
module TestCase
|
|
3
|
+
class MockRequest # :nodoc:
|
|
4
|
+
attr_accessor :controller
|
|
5
|
+
|
|
6
|
+
def initialize(controller)
|
|
7
|
+
self.controller = controller
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def remote_ip
|
|
11
|
+
(controller && controller.respond_to?(:env) && controller.env.is_a?(Hash) && controller.env['REMOTE_ADDR']) || "1.1.1.1"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
def method_missing(*args, &block)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Authlogic
|
|
2
|
+
module TestCase
|
|
3
|
+
# Adapts authlogic to work with the @request object when testing. This way Authlogic can set cookies and what not before
|
|
4
|
+
# a request is made, ultimately letting you log in users in functional tests.
|
|
5
|
+
class RailsRequestAdapter < ControllerAdapters::AbstractAdapter
|
|
6
|
+
def authenticate_with_http_basic(&block)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def cookies
|
|
10
|
+
new_cookies = MockCookieJar.new
|
|
11
|
+
super.each do |key, value|
|
|
12
|
+
new_cookies[key] = value[:value]
|
|
13
|
+
end
|
|
14
|
+
new_cookies
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def cookie_domain
|
|
18
|
+
nil
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def request
|
|
22
|
+
@request ||= MockRequest.new(controller)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def request_content_type
|
|
26
|
+
request.format.to_s
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + "/test_case/rails_request_adapter"
|
|
2
|
+
require File.dirname(__FILE__) + "/test_case/mock_cookie_jar"
|
|
3
|
+
require File.dirname(__FILE__) + "/test_case/mock_controller"
|
|
4
|
+
require File.dirname(__FILE__) + "/test_case/mock_logger"
|
|
5
|
+
require File.dirname(__FILE__) + "/test_case/mock_request"
|
|
6
|
+
|
|
7
|
+
module Authlogic
|
|
8
|
+
# This module is a collection of methods and classes that help you easily test Authlogic. In fact,
|
|
9
|
+
# I use these same tools to test the internals of Authlogic.
|
|
10
|
+
#
|
|
11
|
+
# === The quick and dirty
|
|
12
|
+
#
|
|
13
|
+
# require "authlogic/test_case" # include at the top of test_helper.rb
|
|
14
|
+
# setup :activate_authlogic # run before tests are executed
|
|
15
|
+
# UserSession.create(users(:whomever)) # logs a user in
|
|
16
|
+
#
|
|
17
|
+
# For a more detailed explanation, see below.
|
|
18
|
+
#
|
|
19
|
+
# === Setting up
|
|
20
|
+
#
|
|
21
|
+
# Authlogic comes with some simple testing tools. To get these, you need to first require Authlogic's TestCase. If
|
|
22
|
+
# you are doing this in a rails app, you would require this file at the top of your test_helper.rb file:
|
|
23
|
+
#
|
|
24
|
+
# require "authlogic/test_case"
|
|
25
|
+
#
|
|
26
|
+
# If you are using Test::Unit::TestCase, the standard testing library that comes with ruby, then you can skip this next part.
|
|
27
|
+
# If you are not, you need to include the Authlogic::TestCase into your testing suite as follows:
|
|
28
|
+
#
|
|
29
|
+
# include Authlogic::TestCase
|
|
30
|
+
#
|
|
31
|
+
# Now that everything is ready to go, let's move onto actually testing. Here is the basic idea behind testing:
|
|
32
|
+
#
|
|
33
|
+
# Authlogic requires a "connection" to your controller to activate it. In the same manner that ActiveRecord requires a connection to
|
|
34
|
+
# your database. It can't do anything until it gets connnected. That being said, Authlogic will raise an
|
|
35
|
+
# Authlogic::Session::Activation::NotActivatedError any time you try to instantiate an object without a "connection".
|
|
36
|
+
# So before you do anything with Authlogic, you need to activate / connect Authlogic. Let's walk through how to do this in tests:
|
|
37
|
+
#
|
|
38
|
+
# === Fixtures / Factories
|
|
39
|
+
#
|
|
40
|
+
# Creating users via fixtures / factories is easy. Here's an example of a fixture:
|
|
41
|
+
#
|
|
42
|
+
# ben:
|
|
43
|
+
# email: whatever@whatever.com
|
|
44
|
+
# password_salt: <%= salt = Authlogic::Random.hex_token %>
|
|
45
|
+
# crypted_password: <%= Authlogic::CryptoProviders::Sha512.encrypt("benrocks" + salt) %>
|
|
46
|
+
# persistence_token: <%= Authlogic::Random.hex_token %>
|
|
47
|
+
# single_access_token: <%= Authlogic::Random.friendly_token %>
|
|
48
|
+
# perishable_token: <%= Authlogic::Random.friendly_token %>
|
|
49
|
+
#
|
|
50
|
+
# Notice the crypted_password value. Just supplement that with whatever crypto provider you are using, if you are not using the default.
|
|
51
|
+
#
|
|
52
|
+
# === Functional tests
|
|
53
|
+
#
|
|
54
|
+
# Activating Authlogic isn't a problem here, because making a request will activate Authlogic for you. The problem is
|
|
55
|
+
# logging users in so they can access restricted areas. Solving this is simple, just do this:
|
|
56
|
+
#
|
|
57
|
+
# setup :activate_authlogic
|
|
58
|
+
#
|
|
59
|
+
# For those of you unfamiliar with TestUnit, the setup method bascially just executes a method before any test is ran.
|
|
60
|
+
# It is essentially "setting up" your tests.
|
|
61
|
+
#
|
|
62
|
+
# Once you have done this, just log users in like usual:
|
|
63
|
+
#
|
|
64
|
+
# UserSession.create(users(:whomever))
|
|
65
|
+
# # access my restricted area here
|
|
66
|
+
#
|
|
67
|
+
# Do this before you make your request and it will act as if that user is logged in.
|
|
68
|
+
#
|
|
69
|
+
# === Integration tests
|
|
70
|
+
#
|
|
71
|
+
# Again, just like functional tests, you don't have to do anything. As soon as you make a request, Authlogic will be
|
|
72
|
+
# conntected. If you want to activate Authlogic before making a request follow the same steps described in the
|
|
73
|
+
# "functional tests" section above. It works in the same manner.
|
|
74
|
+
#
|
|
75
|
+
# === Unit tests
|
|
76
|
+
#
|
|
77
|
+
# The only time you need to do any trickiness here is if you want to test Authlogic models. Maybe you added some custom
|
|
78
|
+
# code or methods in your Authlogic models. Maybe you are writing a plugin or a library that extends Authlogic.
|
|
79
|
+
#
|
|
80
|
+
# That being said, in this environment there is no controller. So you need to use a "mock" controller. Something
|
|
81
|
+
# that looks like a controller, acts like a controller, but isn't a "real" controller. You are essentially connecting
|
|
82
|
+
# Authlogic to your "mock" controller, then you can test off of the mock controller to make sure everything is functioning
|
|
83
|
+
# properly.
|
|
84
|
+
#
|
|
85
|
+
# I use a mock controller to test Authlogic myself. It's part of the Authlogic library that you can easily use. It's as simple
|
|
86
|
+
# as functional and integration tests. Just do the following:
|
|
87
|
+
#
|
|
88
|
+
# setup :activate_authlogic
|
|
89
|
+
#
|
|
90
|
+
# You also get a controller method that you can test off of. For example:
|
|
91
|
+
#
|
|
92
|
+
# ben = users(:ben)
|
|
93
|
+
# assert_nil controller.session["user_credentials"]
|
|
94
|
+
# assert UserSession.create(ben)
|
|
95
|
+
# assert_equal controller.session["user_credentials"], ben.persistence_token
|
|
96
|
+
#
|
|
97
|
+
# See how I am checking that Authlogic is interacting with the controller properly? That's the idea here.
|
|
98
|
+
module TestCase
|
|
99
|
+
# Activates authlogic so that you can use it in your tests. You should call this method in your test's setup. Ex:
|
|
100
|
+
#
|
|
101
|
+
# setup :activate_authlogic
|
|
102
|
+
def activate_authlogic
|
|
103
|
+
Authlogic::Session::Base.controller = (@request && Authlogic::TestCase::RailsRequestAdapter.new(@request)) || controller
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# The Authlogic::TestCase::MockController object passed to Authlogic to activate it. You can access this in your test.
|
|
107
|
+
# See the module description for an example.
|
|
108
|
+
def controller
|
|
109
|
+
@controller ||= Authlogic::TestCase::MockController.new
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
::Test::Unit::TestCase.send(:include, TestCase) if defined?(::Test::Unit::TestCase)
|
|
114
|
+
end
|
data/lib/authlogic.rb
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
require "active_support"
|
|
2
2
|
|
|
3
|
-
require File.dirname(__FILE__) + "/authlogic/version"
|
|
4
3
|
require File.dirname(__FILE__) + "/authlogic/i18n"
|
|
5
4
|
require File.dirname(__FILE__) + "/authlogic/random"
|
|
5
|
+
require File.dirname(__FILE__) + "/authlogic/regex"
|
|
6
6
|
|
|
7
7
|
require File.dirname(__FILE__) + "/authlogic/controller_adapters/abstract_adapter"
|
|
8
8
|
require File.dirname(__FILE__) + "/authlogic/controller_adapters/rails_adapter" if defined?(Rails)
|
data/rails/init.rb
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require "authlogic"
|
data/shoulda_macros/authlogic.rb
CHANGED
|
@@ -4,7 +4,8 @@ module Authlogic
|
|
|
4
4
|
def self.should_be_authentic
|
|
5
5
|
klass = model_class
|
|
6
6
|
should "acts as authentic" do
|
|
7
|
-
assert klass.respond_to?(:
|
|
7
|
+
assert klass.new.respond_to?(:password=)
|
|
8
|
+
assert klass.new.respond_to?(:valid_password?)
|
|
8
9
|
end
|
|
9
10
|
end
|
|
10
11
|
end
|
|
@@ -33,7 +33,7 @@ module ActsAsAuthenticTest
|
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
def test_validates_format_of_email_field_options_config
|
|
36
|
-
default = {:with =>
|
|
36
|
+
default = {:with => Authlogic::Regex.email, :message => I18n.t('error_messages.email_invalid', :default => "should look like an email address.")}
|
|
37
37
|
assert_equal default, User.validates_format_of_email_field_options
|
|
38
38
|
assert_equal default, Employee.validates_format_of_email_field_options
|
|
39
39
|
|
|
@@ -43,37 +43,55 @@ module ActsAsAuthenticTest
|
|
|
43
43
|
assert_equal default, User.validates_format_of_email_field_options
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
+
def test_validates_uniqueness_of_email_field_options_config
|
|
47
|
+
default = {:case_sensitive => false, :scope => Employee.validations_scope, :if => "#{Employee.email_field}_changed?".to_sym}
|
|
48
|
+
assert_equal default, Employee.validates_uniqueness_of_email_field_options
|
|
49
|
+
|
|
50
|
+
Employee.validates_uniqueness_of_email_field_options = {:yes => "no"}
|
|
51
|
+
assert_equal({:yes => "no"}, Employee.validates_uniqueness_of_email_field_options)
|
|
52
|
+
Employee.validates_uniqueness_of_email_field_options default
|
|
53
|
+
assert_equal default, Employee.validates_uniqueness_of_email_field_options
|
|
54
|
+
end
|
|
55
|
+
|
|
46
56
|
def test_validates_length_of_email_field
|
|
47
57
|
u = User.new
|
|
48
58
|
u.email = "a@a.a"
|
|
49
59
|
assert !u.valid?
|
|
50
|
-
assert u.errors
|
|
60
|
+
assert u.errors[:email].size > 0
|
|
51
61
|
|
|
52
62
|
u.email = "a@a.com"
|
|
53
63
|
assert !u.valid?
|
|
54
|
-
assert
|
|
64
|
+
assert u.errors[:email].size == 0
|
|
55
65
|
end
|
|
56
66
|
|
|
57
67
|
def test_validates_format_of_email_field
|
|
58
68
|
u = User.new
|
|
59
69
|
u.email = "aaaaaaaaaaaaa"
|
|
60
70
|
assert !u.valid?
|
|
61
|
-
assert u.errors
|
|
71
|
+
assert u.errors[:email].size > 0
|
|
62
72
|
|
|
63
73
|
u.email = "a@a.com"
|
|
64
74
|
assert !u.valid?
|
|
65
|
-
assert
|
|
75
|
+
assert u.errors[:email].size == 0
|
|
76
|
+
|
|
77
|
+
u.email = "dakota.dux+1@gmail.com"
|
|
78
|
+
assert !u.valid?
|
|
79
|
+
assert u.errors[:email].size == 0
|
|
66
80
|
end
|
|
67
81
|
|
|
68
82
|
def test_validates_uniqueness_of_email_field
|
|
69
83
|
u = User.new
|
|
70
84
|
u.email = "bjohnson@binarylogic.com"
|
|
71
85
|
assert !u.valid?
|
|
72
|
-
assert u.errors
|
|
86
|
+
assert u.errors[:email].size > 0
|
|
87
|
+
|
|
88
|
+
u.email = "BJOHNSON@binarylogic.com"
|
|
89
|
+
assert !u.valid?
|
|
90
|
+
assert u.errors[:email].size > 0
|
|
73
91
|
|
|
74
92
|
u.email = "a@a.com"
|
|
75
93
|
assert !u.valid?
|
|
76
|
-
assert
|
|
94
|
+
assert u.errors[:email].size == 0
|
|
77
95
|
end
|
|
78
96
|
end
|
|
79
97
|
end
|