binarylogic-authlogic 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.
Files changed (119) hide show
  1. data/.gitignore +9 -0
  2. data/CHANGELOG.rdoc +334 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +245 -0
  5. data/Rakefile +49 -0
  6. data/VERSION.yml +4 -0
  7. data/generators/session/session_generator.rb +9 -0
  8. data/generators/session/templates/session.rb +2 -0
  9. data/init.rb +1 -0
  10. data/lib/authlogic.rb +55 -0
  11. data/lib/authlogic/acts_as_authentic/base.rb +112 -0
  12. data/lib/authlogic/acts_as_authentic/email.rb +110 -0
  13. data/lib/authlogic/acts_as_authentic/logged_in_status.rb +60 -0
  14. data/lib/authlogic/acts_as_authentic/login.rb +141 -0
  15. data/lib/authlogic/acts_as_authentic/magic_columns.rb +24 -0
  16. data/lib/authlogic/acts_as_authentic/password.rb +344 -0
  17. data/lib/authlogic/acts_as_authentic/perishable_token.rb +105 -0
  18. data/lib/authlogic/acts_as_authentic/persistence_token.rb +68 -0
  19. data/lib/authlogic/acts_as_authentic/restful_authentication.rb +61 -0
  20. data/lib/authlogic/acts_as_authentic/session_maintenance.rb +139 -0
  21. data/lib/authlogic/acts_as_authentic/single_access_token.rb +65 -0
  22. data/lib/authlogic/acts_as_authentic/validations_scope.rb +32 -0
  23. data/lib/authlogic/authenticates_many/association.rb +42 -0
  24. data/lib/authlogic/authenticates_many/base.rb +55 -0
  25. data/lib/authlogic/controller_adapters/abstract_adapter.rb +67 -0
  26. data/lib/authlogic/controller_adapters/merb_adapter.rb +30 -0
  27. data/lib/authlogic/controller_adapters/rails_adapter.rb +38 -0
  28. data/lib/authlogic/crypto_providers/aes256.rb +43 -0
  29. data/lib/authlogic/crypto_providers/bcrypt.rb +89 -0
  30. data/lib/authlogic/crypto_providers/md5.rb +34 -0
  31. data/lib/authlogic/crypto_providers/sha1.rb +35 -0
  32. data/lib/authlogic/crypto_providers/sha512.rb +50 -0
  33. data/lib/authlogic/i18n.rb +63 -0
  34. data/lib/authlogic/random.rb +33 -0
  35. data/lib/authlogic/regex.rb +25 -0
  36. data/lib/authlogic/session/activation.rb +58 -0
  37. data/lib/authlogic/session/active_record_trickery.rb +50 -0
  38. data/lib/authlogic/session/base.rb +37 -0
  39. data/lib/authlogic/session/brute_force_protection.rb +92 -0
  40. data/lib/authlogic/session/callbacks.rb +87 -0
  41. data/lib/authlogic/session/cookies.rb +130 -0
  42. data/lib/authlogic/session/existence.rb +91 -0
  43. data/lib/authlogic/session/foundation.rb +63 -0
  44. data/lib/authlogic/session/http_auth.rb +58 -0
  45. data/lib/authlogic/session/id.rb +41 -0
  46. data/lib/authlogic/session/klass.rb +75 -0
  47. data/lib/authlogic/session/magic_columns.rb +94 -0
  48. data/lib/authlogic/session/magic_states.rb +58 -0
  49. data/lib/authlogic/session/params.rb +100 -0
  50. data/lib/authlogic/session/password.rb +218 -0
  51. data/lib/authlogic/session/perishable_token.rb +18 -0
  52. data/lib/authlogic/session/persistence.rb +70 -0
  53. data/lib/authlogic/session/priority_record.rb +34 -0
  54. data/lib/authlogic/session/scopes.rb +101 -0
  55. data/lib/authlogic/session/session.rb +60 -0
  56. data/lib/authlogic/session/timeout.rb +82 -0
  57. data/lib/authlogic/session/unauthorized_record.rb +50 -0
  58. data/lib/authlogic/session/validation.rb +80 -0
  59. data/lib/authlogic/test_case.rb +114 -0
  60. data/lib/authlogic/test_case/mock_controller.rb +45 -0
  61. data/lib/authlogic/test_case/mock_cookie_jar.rb +14 -0
  62. data/lib/authlogic/test_case/mock_logger.rb +10 -0
  63. data/lib/authlogic/test_case/mock_request.rb +19 -0
  64. data/lib/authlogic/test_case/rails_request_adapter.rb +30 -0
  65. data/rails/init.rb +1 -0
  66. data/shoulda_macros/authlogic.rb +13 -0
  67. data/test/acts_as_authentic_test/base_test.rb +18 -0
  68. data/test/acts_as_authentic_test/email_test.rb +97 -0
  69. data/test/acts_as_authentic_test/logged_in_status_test.rb +36 -0
  70. data/test/acts_as_authentic_test/login_test.rb +109 -0
  71. data/test/acts_as_authentic_test/magic_columns_test.rb +27 -0
  72. data/test/acts_as_authentic_test/password_test.rb +236 -0
  73. data/test/acts_as_authentic_test/perishable_token_test.rb +90 -0
  74. data/test/acts_as_authentic_test/persistence_token_test.rb +55 -0
  75. data/test/acts_as_authentic_test/restful_authentication_test.rb +40 -0
  76. data/test/acts_as_authentic_test/session_maintenance_test.rb +84 -0
  77. data/test/acts_as_authentic_test/single_access_test.rb +44 -0
  78. data/test/authenticates_many_test.rb +16 -0
  79. data/test/crypto_provider_test/aes256_test.rb +14 -0
  80. data/test/crypto_provider_test/bcrypt_test.rb +14 -0
  81. data/test/crypto_provider_test/sha1_test.rb +23 -0
  82. data/test/crypto_provider_test/sha512_test.rb +14 -0
  83. data/test/fixtures/companies.yml +5 -0
  84. data/test/fixtures/employees.yml +17 -0
  85. data/test/fixtures/projects.yml +3 -0
  86. data/test/fixtures/users.yml +24 -0
  87. data/test/libs/affiliate.rb +7 -0
  88. data/test/libs/company.rb +6 -0
  89. data/test/libs/employee.rb +7 -0
  90. data/test/libs/employee_session.rb +2 -0
  91. data/test/libs/ldaper.rb +3 -0
  92. data/test/libs/ordered_hash.rb +9 -0
  93. data/test/libs/project.rb +3 -0
  94. data/test/libs/user.rb +5 -0
  95. data/test/libs/user_session.rb +2 -0
  96. data/test/random_test.rb +49 -0
  97. data/test/session_test/activation_test.rb +43 -0
  98. data/test/session_test/active_record_trickery_test.rb +27 -0
  99. data/test/session_test/brute_force_protection_test.rb +101 -0
  100. data/test/session_test/callbacks_test.rb +6 -0
  101. data/test/session_test/cookies_test.rb +107 -0
  102. data/test/session_test/credentials_test.rb +0 -0
  103. data/test/session_test/existence_test.rb +64 -0
  104. data/test/session_test/http_auth_test.rb +28 -0
  105. data/test/session_test/id_test.rb +17 -0
  106. data/test/session_test/klass_test.rb +35 -0
  107. data/test/session_test/magic_columns_test.rb +62 -0
  108. data/test/session_test/magic_states_test.rb +60 -0
  109. data/test/session_test/params_test.rb +53 -0
  110. data/test/session_test/password_test.rb +92 -0
  111. data/test/session_test/perishability_test.rb +15 -0
  112. data/test/session_test/persistence_test.rb +21 -0
  113. data/test/session_test/scopes_test.rb +60 -0
  114. data/test/session_test/session_test.rb +59 -0
  115. data/test/session_test/timeout_test.rb +52 -0
  116. data/test/session_test/unauthorized_record_test.rb +13 -0
  117. data/test/session_test/validation_test.rb +23 -0
  118. data/test/test_helper.rb +174 -0
  119. metadata +227 -0
@@ -0,0 +1,100 @@
1
+ module Authlogic
2
+ module Session
3
+ # This module is responsible for authenticating the user via params, which ultimately allows the user to log in using a URL like the following:
4
+ #
5
+ # https://www.domain.com?user_credentials=4LiXF7FiGUppIPubBPey
6
+ #
7
+ # Notice the token in the URL, this is a single access token. A single access token is used for single access only, it is not persisted. Meaning the user
8
+ # provides it, Authlogic grants them access, and that's it. If they want access again they need to provide the token again. Authlogic will
9
+ # *NEVER* try to persist the session after authenticating through this method.
10
+ #
11
+ # For added security, this token is *ONLY* allowed for RSS and ATOM requests. You can change this with the configuration. You can also define if
12
+ # it is allowed dynamically by defining a single_access_allowed? method in your controller. For example:
13
+ #
14
+ # class UsersController < ApplicationController
15
+ # private
16
+ # def single_access_allowed?
17
+ # action_name == "index"
18
+ # end
19
+ #
20
+ # Also, by default, this token is permanent. Meaning if the user changes their password, this token will remain the same. It will only change
21
+ # when it is explicitly reset.
22
+ #
23
+ # You can modify all of this behavior with the Config sub module.
24
+ module Params
25
+ def self.included(klass)
26
+ klass.class_eval do
27
+ extend Config
28
+ include InstanceMethods
29
+ attr_accessor :single_access
30
+ persist :persist_by_params
31
+ end
32
+ end
33
+
34
+ # Configuration for the params / single access feature.
35
+ module Config
36
+ # Works exactly like cookie_key, but for params. So a user can login via params just like a cookie or a session. Your URL would look like:
37
+ #
38
+ # http://www.domain.com?user_credentials=my_single_access_key
39
+ #
40
+ # You can change the "user_credentials" key above with this configuration option. Keep in mind, just like cookie_key, if you supply an id
41
+ # the id will be appended to the front. Check out cookie_key for more details. Also checkout the "Single Access / Private Feeds Access" section in the README.
42
+ #
43
+ # * <tt>Default:</tt> cookie_key
44
+ # * <tt>Accepts:</tt> String
45
+ def params_key(value = nil)
46
+ rw_config(:params_key, value, cookie_key)
47
+ end
48
+ alias_method :params_key=, :params_key
49
+
50
+ # Authentication is allowed via a single access token, but maybe this is something you don't want for your application as a whole. Maybe this is something you only want for specific request types.
51
+ # Specify a list of allowed request types and single access authentication will only be allowed for the ones you specify.
52
+ #
53
+ # * <tt>Default:</tt> ["application/rss+xml", "application/atom+xml"]
54
+ # * <tt>Accepts:</tt> String of a request type, or :all or :any to allow single access authentication for any and all request types
55
+ def single_access_allowed_request_types(value = nil)
56
+ rw_config(:single_access_allowed_request_types, value, ["application/rss+xml", "application/atom+xml"])
57
+ end
58
+ alias_method :single_access_allowed_request_types=, :single_access_allowed_request_types
59
+ end
60
+
61
+ # The methods available for an Authlogic::Session::Base object that make up the params / single access feature.
62
+ module InstanceMethods
63
+ private
64
+ def persist_by_params
65
+ return false if !params_enabled?
66
+ self.unauthorized_record = search_for_record("find_by_single_access_token", params_credentials)
67
+ self.single_access = valid?
68
+ end
69
+
70
+ def params_enabled?
71
+ return false if !params_credentials || !klass.column_names.include?("single_access_token")
72
+ return controller.single_access_allowed? if controller.responds_to_single_access_allowed?
73
+
74
+ case single_access_allowed_request_types
75
+ when Array
76
+ single_access_allowed_request_types.include?(controller.request_content_type) || single_access_allowed_request_types.include?(:all)
77
+ else
78
+ [:all, :any].include?(single_access_allowed_request_types)
79
+ end
80
+ end
81
+
82
+ def params_key
83
+ build_key(self.class.params_key)
84
+ end
85
+
86
+ def single_access?
87
+ single_access == true
88
+ end
89
+
90
+ def single_access_allowed_request_types
91
+ self.class.single_access_allowed_request_types
92
+ end
93
+
94
+ def params_credentials
95
+ controller.params[params_key]
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,218 @@
1
+ module Authlogic
2
+ module Session
3
+ # Handles authenticating via a traditional username and password.
4
+ module Password
5
+ def self.included(klass)
6
+ klass.class_eval do
7
+ extend Config
8
+ include InstanceMethods
9
+ validate :validate_by_password, :if => :authenticating_with_password?
10
+
11
+ class << self
12
+ attr_accessor :configured_password_methods
13
+ end
14
+ end
15
+ end
16
+
17
+ # Password configuration
18
+ module Config
19
+ # Authlogic tries to validate the credentials passed to it. One part of validation is actually finding the user and making sure it exists. What method it uses the do this is up to you.
20
+ #
21
+ # Let's say you have a UserSession that is authenticating a User. By default UserSession will call User.find_by_login(login). You can change what method UserSession calls by specifying it here. Then
22
+ # in your User model you can make that method do anything you want, giving you complete control of how users are found by the UserSession.
23
+ #
24
+ # Let's take an example: You want to allow users to login by username or email. Set this to the name of the class method that does this in the User model. Let's call it "find_by_username_or_email"
25
+ #
26
+ # class User < ActiveRecord::Base
27
+ # def self.find_by_username_or_email(login)
28
+ # find_by_username(login) || find_by_email(login)
29
+ # end
30
+ # end
31
+ #
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"
36
+ # * <tt>Accepts:</tt> Symbol or String
37
+ def find_by_login_method(value = nil)
38
+ rw_config(:find_by_login_method, value, "find_by_smart_case_login_field")
39
+ end
40
+ alias_method :find_by_login_method=, :find_by_login_method
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
+
71
+ # The name of the method you want Authlogic to create for storing the login / username. Keep in mind this is just for your
72
+ # Authlogic::Session, if you want it can be something completely different than the field in your model. So if you wanted people to
73
+ # login with a field called "login" and then find users by email this is compeltely doable. See the find_by_login_method configuration
74
+ # option for more details.
75
+ #
76
+ # * <tt>Default:</tt> klass.login_field || klass.email_field
77
+ # * <tt>Accepts:</tt> Symbol or String
78
+ def login_field(value = nil)
79
+ rw_config(:login_field, value, klass.login_field || klass.email_field)
80
+ end
81
+ alias_method :login_field=, :login_field
82
+
83
+ # Works exactly like login_field, but for the password instead. Returns :password if a login_field exists.
84
+ #
85
+ # * <tt>Default:</tt> :password
86
+ # * <tt>Accepts:</tt> Symbol or String
87
+ def password_field(value = nil)
88
+ rw_config(:password_field, value, login_field && :password)
89
+ end
90
+ alias_method :password_field=, :password_field
91
+
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.
93
+ #
94
+ # * <tt>Default:</tt> "valid_password?"
95
+ # * <tt>Accepts:</tt> Symbol or String
96
+ def verify_password_method(value = nil)
97
+ rw_config(:verify_password_method, value, "valid_password?")
98
+ end
99
+ alias_method :verify_password_method=, :verify_password_method
100
+ end
101
+
102
+ # Password related instance methods
103
+ module InstanceMethods
104
+ def initialize(*args)
105
+ if !self.class.configured_password_methods
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)
114
+
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
123
+
124
+ self.class.configured_password_methods = true
125
+ end
126
+
127
+ super
128
+ end
129
+
130
+ # Returns the login_field / password_field credentials combination in hash form.
131
+ def credentials
132
+ if authenticating_with_password?
133
+ details = {}
134
+ details[login_field.to_sym] = send(login_field)
135
+ details[password_field.to_sym] = "<protected>"
136
+ details
137
+ else
138
+ super
139
+ end
140
+ end
141
+
142
+ # Accepts the login_field / password_field credentials combination in hash form.
143
+ def credentials=(value)
144
+ super
145
+ values = value.is_a?(Array) ? value : [value]
146
+ if values.first.is_a?(Hash)
147
+ values.first.with_indifferent_access.slice(login_field, password_field).each do |field, value|
148
+ next if value.blank?
149
+ send("#{field}=", value)
150
+ end
151
+ end
152
+ end
153
+
154
+ def invalid_password?
155
+ invalid_password == true
156
+ end
157
+
158
+ private
159
+ def authenticating_with_password?
160
+ login_field && (!send(login_field).nil? || !send("protected_#{password_field}").nil?)
161
+ end
162
+
163
+ def validate_by_password
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?
168
+ return if errors.count > 0
169
+
170
+ self.attempted_record = search_for_record(find_by_login_method, send(login_field))
171
+
172
+ if attempted_record.blank?
173
+ generalize_credentials_error_messages? ? add_general_credentials_error : errors.add(login_field, I18n.t('error_messages.login_not_found', :default => "is not valid"))
174
+ return
175
+ end
176
+
177
+ if !attempted_record.send(verify_password_method, send("protected_#{password_field}"))
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"))
180
+ return
181
+ end
182
+ end
183
+
184
+ def invalid_password
185
+ @invalid_password
186
+ end
187
+
188
+ def invalid_password=(value)
189
+ @invalid_password = value
190
+ end
191
+
192
+ def find_by_login_method
193
+ self.class.find_by_login_method
194
+ end
195
+
196
+ def login_field
197
+ self.class.login_field
198
+ end
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
+
208
+ def password_field
209
+ self.class.password_field
210
+ end
211
+
212
+ def verify_password_method
213
+ self.class.verify_password_method
214
+ end
215
+ end
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,18 @@
1
+ module Authlogic
2
+ module Session
3
+ # Maintains the perishable token, which is helpful for confirming records or authorizing records to reset their password. All that this
4
+ # module does is reset it after a session have been saved, just keep it changing. The more it changes, the tighter the security.
5
+ #
6
+ # See Authlogic::ActsAsAuthentic::PerishableToken for more information.
7
+ module PerishableToken
8
+ def self.included(klass)
9
+ klass.after_save :reset_perishable_token!
10
+ end
11
+
12
+ private
13
+ def reset_perishable_token!
14
+ record.reset_perishable_token if record.respond_to?(:reset_perishable_token) && !record.disable_perishable_token_maintenance?
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,70 @@
1
+ module Authlogic
2
+ module Session
3
+ # Responsible for allowing you to persist your sessions.
4
+ module Persistence
5
+ def self.included(klass)
6
+ klass.class_eval do
7
+ extend ClassMethods
8
+ include InstanceMethods
9
+ end
10
+ end
11
+
12
+ module ClassMethods
13
+ # This is how you persist a session. This finds the record for the current session using
14
+ # a variety of methods. It basically tries to "log in" the user without the user having
15
+ # to explicitly log in. Check out the other Authlogic::Session modules for more information.
16
+ #
17
+ # The best way to use this method is something like:
18
+ #
19
+ # helper_method :current_user_session, :current_user
20
+ #
21
+ # def current_user_session
22
+ # return @current_user_session if defined?(@current_user_session)
23
+ # @current_user_session = UserSession.find
24
+ # end
25
+ #
26
+ # def current_user
27
+ # return @current_user if defined?(@current_user)
28
+ # @current_user = current_user_session && current_user_session.user
29
+ # end
30
+ #
31
+ # Also, this method accepts a single parameter as the id, to find session that you marked with an id:
32
+ #
33
+ # UserSession.find(:secure)
34
+ #
35
+ # See the id method for more information on ids.
36
+ def find(id = nil, priority_record = nil)
37
+ session = new({:priority_record => priority_record}, id)
38
+ session.priority_record = priority_record
39
+ if session.persisting?
40
+ session
41
+ else
42
+ nil
43
+ end
44
+ end
45
+ end
46
+
47
+ module InstanceMethods
48
+ # Let's you know if the session is being persisted or not, meaning the user does not have to explicitly log in
49
+ # in order to be logged in. If the session has no associated record, it will try to find a record and persis
50
+ # the session. This is the method that the class level method find uses to ultimately persist the session.
51
+ def persisting?
52
+ return true if !record.nil?
53
+ self.attempted_record = nil
54
+ before_persisting
55
+ persist
56
+ ensure_authentication_attempted
57
+ if errors.empty? && !attempted_record.nil?
58
+ self.record = attempted_record
59
+ after_persisting
60
+ save_record
61
+ self.new_session = false
62
+ true
63
+ else
64
+ false
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,34 @@
1
+ module Authlogic
2
+ module Session
3
+ # The point of this module is to avoid the StaleObjectError raised when lock_version is implemented in ActiveRecord.
4
+ # We accomplish this by using a "priority record". Meaning this record is used if possible, it gets priority.
5
+ # This way we don't save a record behind the scenes thus making an object being used stale.
6
+ module PriorityRecord
7
+ def self.included(klass)
8
+ klass.class_eval do
9
+ attr_accessor :priority_record
10
+ end
11
+ end
12
+
13
+ # Setting priority record if it is passed. The only way it can be passed is through an array:
14
+ #
15
+ # session.credentials = [real_user_object, priority_user_object]
16
+ def credentials=(value)
17
+ super
18
+ values = value.is_a?(Array) ? value : [value]
19
+ self.priority_record = values[1] if values[1].class < ::ActiveRecord::Base
20
+ end
21
+
22
+ private
23
+ def attempted_record=(value)
24
+ value = priority_record if value == priority_record
25
+ super
26
+ end
27
+
28
+ def save_record(alternate_record = nil)
29
+ r = alternate_record || record
30
+ super if r != priority_record
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,101 @@
1
+ module Authlogic
2
+ module Session
3
+ # Authentication can be scoped, and it's easy, you just need to define how you want to scope everything. This should help you:
4
+ #
5
+ # 1. Want to scope by a parent object? Ex: An account has many users. Checkout Authlogic::AuthenticatesMany
6
+ # 2. Want to scope the validations in your model? Ex: 2 users can have the same login under different accounts. See Authlogic::ActsAsAuthentic::Scope
7
+ module Scopes # :nodoc:
8
+ def self.included(klass)
9
+ klass.class_eval do
10
+ extend ClassMethods
11
+ include InstanceMethods
12
+ attr_writer :scope
13
+ end
14
+ end
15
+
16
+ # = Scopes
17
+ module ClassMethods
18
+ # The current scope set, should be used in the block passed to with_scope.
19
+ def scope
20
+ Thread.current[:authlogic_scope]
21
+ end
22
+
23
+ # 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
24
+ # ActiveRecord::Base#with_scopes. It accepts a hash with any of the following options:
25
+ #
26
+ # * <tt>find_options:</tt> any options you can pass into ActiveRecord::Base.find. This is used when trying to find the record.
27
+ # * <tt>id:</tt> The id of the session, this gets merged with the real id. For information ids see the id method.
28
+ #
29
+ # Here is how you use it:
30
+ #
31
+ # UserSession.with_scope(:find_options => {:conditions => "account_id = 2"}, :id => "account_2") do
32
+ # UserSession.find
33
+ # end
34
+ #
35
+ # Eseentially what the above does is scope the searching of the object with the sql you provided. So instead of:
36
+ #
37
+ # User.find(:first, :conditions => "login = 'ben'")
38
+ #
39
+ # it would be:
40
+ #
41
+ # User.find(:first, :conditions => "login = 'ben' and account_id = 2")
42
+ #
43
+ # You will also notice the :id option. This works just like the id method. It scopes your cookies. So the name of your cookie will be:
44
+ #
45
+ # account_2_user_credentials
46
+ #
47
+ # instead of:
48
+ #
49
+ # user_credentials
50
+ #
51
+ # What is also nifty about scoping with an :id is that it merges your id's. So if you do:
52
+ #
53
+ # UserSession.with_scope(:find_options => {:conditions => "account_id = 2"}, :id => "account_2") do
54
+ # session = UserSession.new
55
+ # session.id = :secure
56
+ # end
57
+ #
58
+ # The name of your cookies will be:
59
+ #
60
+ # secure_account_2_user_credentials
61
+ def with_scope(options = {}, &block)
62
+ raise ArgumentError.new("You must provide a block") unless block_given?
63
+ self.scope = options
64
+ result = yield
65
+ self.scope = nil
66
+ result
67
+ end
68
+
69
+ private
70
+ def scope=(value)
71
+ Thread.current[:authlogic_scope] = value
72
+ end
73
+ end
74
+
75
+ module InstanceMethods
76
+ # Setting the scope if it exists upon instantiation.
77
+ def initialize(*args)
78
+ self.scope = self.class.scope
79
+ super
80
+ end
81
+
82
+ # The scope of the current object
83
+ def scope
84
+ @scope ||= {}
85
+ end
86
+
87
+ private
88
+ # Used for things like cookie_key, session_key, etc.
89
+ def build_key(last_part)
90
+ [scope[:id], super].compact.join("_")
91
+ end
92
+
93
+ def search_for_record(*args)
94
+ klass.send(:with_scope, :find => (scope[:find_options] || {})) do
95
+ klass.send(*args)
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end