authlogic_crowd 0.2.4 → 0.3.0.pre.2

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.
@@ -1,26 +1,32 @@
1
- module AuthlogicCrowd
2
- module ActsAsAuthenticCallbacks
3
- def self.included(klass)
4
- klass.class_eval do
5
- add_acts_as_authentic_module(Methods, :prepend)
6
- end
7
- end
8
- module Methods
9
- METHODS = [
10
- "sync_on_create"
11
- ]
12
- def self.included(base)
13
- base.send :include, ActiveSupport::Callbacks
14
- base.define_callbacks *METHODS
15
- end
16
- private
17
- METHODS.each do |method|
18
- class_eval <<-"end_eval", __FILE__, __LINE__
19
- def #{method}
20
- run_callbacks(:#{method}) { |result, object| result == false }
21
- end
22
- end_eval
23
- end
24
- end
25
- end
26
- end
1
+ module AuthlogicCrowd
2
+ module ActsAsAuthenticCallbacks
3
+ METHODS = [
4
+ # Fired when a local record is synced from a crowd record (usually on login)
5
+ "before_sync_from_crowd", "sync_from_crowd", "after_sync_from_crowd",
6
+
7
+ # Fired when a new local record is created by crowd (usually because a
8
+ # user logged in using crowd credentials)
9
+ "before_create_from_crowd", "after_create_from_crowd",
10
+
11
+ # Fired when a local record is synced to a crowd record (usually when creating a local record)
12
+ "before_sync_to_crowd", "sync_to_crowd", "after_sync_to_crowd",
13
+
14
+ # Fired when a creating a new crowd record (usually because a new local
15
+ # record was created)
16
+ "before_create_crowd_record", "after_create_crowd_record",
17
+ ]
18
+
19
+ def self.included(klass)
20
+ klass.define_callbacks *METHODS
21
+ end
22
+
23
+ private
24
+ METHODS.each do |method|
25
+ class_eval <<-"end_eval", __FILE__, __LINE__
26
+ def #{method}
27
+ run_callbacks(:#{method}) { |result, object| result == false }
28
+ end
29
+ end_eval
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,97 @@
1
+ module AuthlogicCrowd
2
+ class CrowdSynchronizer
3
+
4
+ attr_accessor :klass, :crowd_client, :local_record
5
+
6
+ def initialize(klass, crowd_client, local_record=nil, crowd_record=nil)
7
+ self.klass = klass
8
+ self.crowd_client = crowd_client
9
+ self.local_record = local_record if local_record
10
+ self.crowd_record = crowd_record if crowd_record
11
+ @syncing = false
12
+ end
13
+
14
+ def local_record=(val)
15
+ @local_record = val
16
+ @local_record.crowd_synchronizer = self if @local_record
17
+ @local_record.crowd_record = @crowd_record if @local_record && @crowd_record
18
+ end
19
+
20
+ def crowd_record
21
+ @crowd_record ||= @local_record.crowd_record if @local_record
22
+ @crowd_record
23
+ end
24
+
25
+ def crowd_record=(val)
26
+ @crowd_record = val
27
+ @local_record.crowd_record = @crowd_record if @local_record
28
+ end
29
+
30
+ def create_crowd_record
31
+ if local_record.before_create_crowd_record
32
+ self.crowd_record = SimpleCrowd::User.new({:username => local_record.send(klass.login_field)})
33
+ if sync_to_crowd(true)
34
+ local_record.after_create_crowd_record
35
+ return crowd_record
36
+ end
37
+ end
38
+ nil
39
+ end
40
+
41
+ def sync_to_crowd(new_record=false)
42
+ return unless local_record && crowd_record
43
+ return if @syncing
44
+
45
+ if local_record.before_sync_to_crowd
46
+ @syncing = true
47
+ begin
48
+ local_record.sync_to_crowd
49
+ if new_record || crowd_record.dirty? || local_record.crowd_password_changed?
50
+ if new_record
51
+ crowd_client.add_user crowd_record, local_record.crowd_password
52
+ else
53
+ crowd_client.update_user crowd_record
54
+ if local_record.crowd_password_changed? && local_record.crowd_password
55
+ crowd_client.update_user_credential crowd_record.username, local_record.crowd_password
56
+ end
57
+ end
58
+ end
59
+ ensure
60
+ @syncing = false
61
+ end
62
+ local_record.after_sync_to_crowd
63
+ return true
64
+ end
65
+ false
66
+ end
67
+
68
+ def create_record_from_crowd
69
+ self.local_record = klass.new
70
+ if local_record.before_create_from_crowd
71
+ local_record.send(:"#{klass.login_field}=", crowd_record.username) if local_record.respond_to?(:"#{klass.login_field}=")
72
+ sync_from_crowd
73
+ if local_record.save_without_session_maintenance
74
+ local_record.after_create_from_crowd
75
+ return local_record
76
+ end
77
+ end
78
+ nil
79
+ end
80
+
81
+ def sync_from_crowd
82
+ return unless local_record && crowd_record
83
+ return if @syncing
84
+
85
+ if local_record.before_sync_from_crowd
86
+ @syncing = true
87
+ begin
88
+ local_record.sync_from_crowd
89
+ local_record.save_without_session_maintenance if local_record.changed?
90
+ ensure
91
+ @syncing = false
92
+ end
93
+ local_record.after_sync_from_crowd
94
+ end
95
+ end
96
+ end
97
+ end
@@ -1,302 +1,358 @@
1
- module AuthlogicCrowd
2
- module Session
3
- def self.included(klass)
4
- klass.class_eval do
5
- extend Config
6
- include Methods
7
- end
8
- end
9
- module Config
10
-
11
- # Single Signout (defaults to true)
12
- # @param [Boolean] value
13
- def crowd_sso(value=nil)
14
- rw_config(:crowd_sso, value, true)
15
- end
16
- alias_method :crowd_sso=, :crowd_sso
17
-
18
- def crowd_sso?
19
- crowd_sso
20
- end
21
-
22
- # Auto Register is enabled by default.
23
- # Add this in your Session object if you need to disable auto-registration via crowd
24
- def auto_register(value=true)
25
- auto_register_value(value)
26
- end
27
- def auto_register_value(value=nil)
28
- rw_config(:auto_register,value,true)
29
- end
30
- alias_method :auto_register=,:auto_register
31
-
32
- def crowd_user_token= token
33
- # Dup token before storing to make sure we store a pure string to avoid session serialization issues.
34
- token = token.dup if token
35
- session_user_token = controller && controller.session[:"crowd.token_key"]
36
- cookie_user_token = crowd_sso? && controller && controller.cookies[:"crowd.token_key"]
37
- @crowd_client ||= SimpleCrowd::Client.new({
38
- :service_url => klass.crowd_service_url,
39
- :app_name => klass.crowd_app_name,
40
- :app_password => klass.crowd_app_password})
41
- crowd_cookie_info = self.crowd_cookie_info(@crowd_client)
42
- controller.session[:"crowd.token_key"] = token unless session_user_token == token || !controller
43
- controller.cookies[:"crowd.token_key"] = {:domain => crowd_cookie_info[:domain],
44
- :secure => crowd_cookie_info[:secure],
45
- :value => token} unless cookie_user_token == token || !crowd_sso? || !controller
46
- end
47
- def crowd_user_token
48
- controller && (controller.params["crowd.token_key"] || controller.cookies[:"crowd.token_key"] || controller.session[:"crowd.token_key"])
49
- end
50
-
51
- def crowd_cookie_info(crowd_client)
52
- Rails.cache.fetch('crowd_cookie_info') do
53
- # Strings returned by crowd contain singleton methods which cannot
54
- # be serialized into the Rails.cache. Do a shallow dup of each string
55
- # in the returned hash
56
- crowd_client.get_cookie_info.inject({}) do |cookie_info, (key, val)|
57
- cookie_info[key] = val ? val.dup : val
58
- cookie_info
59
- end
60
- end
61
- end
62
- end
63
- module Methods
64
- def self.included(klass)
65
- klass.class_eval do
66
- attr_accessor :new_registration
67
- validate :validate_by_crowd, :if => :authenticating_with_crowd?
68
- persist :validate_by_crowd, :if => :authenticating_with_crowd?
69
- after_create :sync_with_crowd, :if => :authenticating_with_crowd?
70
- before_destroy :logout_of_crowd, :if => [:authenticating_with_crowd?, :sso?]
71
- end
72
- end
73
-
74
- # Temporary storage of crowd record for syncing purposes
75
- attr_accessor :crowd_record
76
-
77
- # Determines if the authenticated user is also a new registration.
78
- # For use in the session controller to help direct the most appropriate action to follow.
79
- def new_registration?
80
- new_registration || !new_registration.nil?
81
- end
82
-
83
- # Determines if the authenticated user has a complete registration (no validation errors)
84
- # For use in the session controller to help direct the most appropriate action to follow.
85
- def registration_complete?
86
- attempted_record && attempted_record.valid?
87
- end
88
-
89
- def auto_register?
90
- self.class.auto_register_value
91
- end
92
-
93
- protected
94
-
95
- # Determines whether to use crowd to authenticate and validate the current request
96
- # For now we assume the app wants to use Crowd exclusively.
97
- # Use crowd authentication if tokens are present or if login/password is available but not valid or is blank in db
98
- # TODO: Add flexibility regarding multiple authentication methods and identity mapping
99
- def authenticating_with_crowd?
100
- errors.empty? &&
101
- !klass.crowd_app_name.blank? &&
102
- !klass.crowd_app_password.blank? &&
103
- ((login_field && (!send(login_field).nil? || !send("protected_#{password_field}").nil?)) ||
104
- controller && (controller.cookies[:"crowd.token_key"] || controller.session[:"crowd.token_key"] || controller.params["crowd.token_key"]))
105
- end
106
-
107
- def authenticating_with_password?
108
- !authenticating_with_crowd? && login_field && (!send(login_field).nil? || !send("protected_#{password_field}").nil?)
109
- end
110
-
111
- def sso?
112
- self.class.crowd_sso
113
- end
114
-
115
- # def credentials
116
- # if authenticating_with_crowd?
117
- # details = {}
118
- # details[login_field.to_sym] = send(login_field)
119
- # details[password_field.to_sym] = "<protected>"
120
- # details[crowd_user_token_field.to_sym] = send(crowd_user_token_field)
121
- # details
122
- # else
123
- # super
124
- # end
125
- # end
126
-
127
- def credentials=(value)
128
- super
129
- values = value.is_a?(Array) ? value : [value]
130
- if values.first.is_a?(Hash)
131
- values.first.with_indifferent_access.slice(login_field, password_field).each do |field, value|
132
- next if value.blank?
133
- send("#{field}=", value)
134
- end
135
- end
136
- end
137
-
138
- private
139
-
140
- # Main session validation using Crowd user token.
141
- # Uses simple_crowd to verify the user token on the configured crowd server
142
- # If no *local* user is found and auto_register is enabled (default) then automatically create *local* user for them
143
- # TODO: Cleanup and figure out reason for duplicate calls
144
- def validate_by_crowd
145
- begin
146
- load_crowd_app_token
147
- login = send(login_field) || (unauthorized_record && unauthorized_record.login)
148
- password = send("protected_#{password_field}")
149
- params_user_token = controller && controller.params["crowd.token_key"]
150
- session_user_token = controller && controller.session[:"crowd.token_key"]
151
- cookie_user_token = sso? && controller && controller.cookies[:"crowd.token_key"]
152
- user_token = crowd_user_token
153
- crowd_user = nil
154
-
155
- # Lets see if the user passed in an email or a login using the db
156
- if !login.blank? && self.unauthorized_record.nil?
157
- self.unauthorized_record = klass.respond_to?(:login_or_email_equals) ?
158
- klass.send(:login_or_email_equals, login).first :
159
- klass.send(find_by_login_method, login)
160
- # If passed in login equals the user email then get the REAL login used by crowd instead
161
- login = unauthorized_record.login if !unauthorized_record.nil? && login = unauthorized_record.email
162
- end
163
-
164
- if user_token && crowd_client.is_valid_user_token?(user_token)
165
- elsif login && password
166
- # Authenticate if we don't have token
167
- user_token = crowd_client.authenticate_user login, password rescue nil
168
-
169
- # Try one last time to login with crowd email field instead of login
170
- if user_token.nil? && self.unauthorized_record.nil? && login =~ Authlogic::Regex.email
171
- crowd_user ||= crowd_client.find_user_by_email(login)
172
- if crowd_user
173
- login = crowd_user.username
174
- user_token = crowd_client.authenticate_user login, password rescue nil
175
- end
176
- end
177
- else
178
- user_token = nil
179
- end
180
-
181
- raise SimpleCrowd::CrowdError.new "No user token" if user_token.blank?
182
-
183
- login = crowd_client.find_username_by_token user_token unless login &&
184
- (!cookie_user_token || session_user_token == cookie_user_token) &&
185
- (!params_user_token || session_user_token == params_user_token)
186
- send("#{login_field}=", login)
187
-
188
- self.class.crowd_user_token = user_token
189
-
190
- if !self.unauthorized_record.nil? && self.unauthorized_record.login == login
191
- self.attempted_record = self.unauthorized_record
192
- else
193
- self.attempted_record = self.unauthorized_record = klass.send(find_by_login_method, login)
194
- end
195
-
196
- if !attempted_record
197
- # If auto_register enabled then create new user with crowd info
198
- if auto_register?
199
- crowd_user ||= crowd_client.find_user_by_token user_token
200
- self.attempted_record = klass.new :login => crowd_user.username, :email => crowd_user.email
201
- self.new_registration = true
202
- self.attempted_record.crowd_record = crowd_user
203
- self.crowd_record = crowd_user
204
- sync
205
- self.attempted_record.save_without_session_maintenance
206
- else
207
- errors.add_to_base("We did not find any accounts with that login. Enter your details and create an account.")
208
- return false
209
- end
210
- end
211
- rescue Exception => e
212
- errors.add_to_base("Authentication failed. Please try again")
213
- # Don't know why it doesn't work the first time,
214
- # but if we nil the session key here then the session doesn't get deleted
215
- # Leaving the token triggers the validation a second time and successfully destroys the session
216
- # REMOVED AS HACK
217
- # Hack to fix user_credentials not being deleted on session destroy
218
-
219
- if controller
220
- controller.session[:"crowd.token_key"] = nil
221
- unless (send(login_field) || (unauthorized_record && unauthorized_record.login) && send("protected_#{password_field}"))
222
- # Hack to try and check session without recreating it. (we only want to destroy it if it exists already)
223
- # This is to avoid a infinite recursive session creation bug we had a while back
224
- curr_klass = klass
225
- curr_session = controller.instance_eval { @current_user_session }
226
- curr_session.destroy if curr_session
227
- controller.session.clear
228
- end
229
- # Delete by klass name instead of generic user
230
- controller.cookies.delete :"#{klass.name.underscore}_credentials"
231
- controller.cookies.delete :"crowd.token_key", :domain => crowd_cookie_info[:domain] if sso?
232
- end
233
- raise unless e.kind_of? SimpleCrowd::CrowdError
234
- false
235
- end
236
- end
237
-
238
- def sync_with_crowd
239
- # If it's a new registration then the crowd data was just pulled, so skip syncing on login
240
- unless new_registration? || !self.attempted_record
241
- login = send(login_field) || (!attempted_record.nil? && attempted_record.login)
242
- user_token = controller && (controller.params["crowd.token_key"] || controller.cookies[:"crowd.token_key"] || controller.session[:"crowd.token_key"])
243
- crowd_user = if login
244
- crowd_client.find_user_by_name login
245
- elsif user_token
246
- crowd_client.find_user_by_token user_token
247
- end
248
- if crowd_user && before_sync
249
- self.crowd_record = crowd_user
250
- # Callbacks to sync data
251
- sync
252
- self.attempted_record.save
253
- after_sync
254
- end
255
- end
256
- end
257
-
258
- # Single Sign-out
259
- def logout_of_crowd
260
- # Send an invalidate call for single signout
261
- # Apparently there is no way of knowing if this was successful or not.
262
- crowd_client.invalidate_user_token crowd_user_token unless crowd_user_token.nil?
263
- # Remove cookie and session
264
- if controller
265
- controller.session[:"crowd.token_key"] = nil
266
- controller.cookies.delete :"crowd.token_key", :domain => crowd_cookie_info[:domain] if sso?
267
- controller.cookies.delete :"#{klass.name.underscore}_credentials"
268
- end
269
- true
270
- end
271
-
272
-
273
- def crowd_user_token
274
- self.class.crowd_user_token
275
- end
276
- def crowd_client
277
- @crowd_client ||= SimpleCrowd::Client.new(crowd_config)
278
- end
279
- def load_crowd_app_token
280
- crowd_app_token = Rails.cache.read('crowd_app_token')
281
- if crowd_app_token.nil?
282
- crowd_app_token = crowd_client.app_token
283
- # Strings returned by crowd contain singleton methods which cannot
284
- # be serialized into the Rails.cache. Duping the strings removes the
285
- # singleton methods.
286
- Rails.cache.write('crowd_app_token', crowd_app_token.dup)
287
- else
288
- crowd_client.app_token = crowd_app_token
289
- end
290
- crowd_app_token
291
- end
292
- def crowd_cookie_info
293
- @crowd_cookie_info ||= self.class.crowd_cookie_info(crowd_client)
294
- end
295
- def crowd_config
296
- {:service_url => klass.crowd_service_url,
297
- :app_name => klass.crowd_app_name,
298
- :app_password => klass.crowd_app_password}
299
- end
300
- end
301
- end
302
- end
1
+ module AuthlogicCrowd
2
+ module Session
3
+ def self.included(klass)
4
+ klass.class_eval do
5
+ extend Config
6
+ include InstanceMethods
7
+
8
+ attr_accessor :new_registration
9
+
10
+ persist :persist_by_crowd, :if => :authenticating_with_crowd?
11
+ validate :validate_by_crowd, :if => [:authenticating_with_crowd?, :needs_crowd_validation?]
12
+ before_destroy :logout_of_crowd, :if => :authenticating_with_crowd?
13
+ after_create(:if => :authenticating_with_crowd?, :unless => :new_registration?) do |s|
14
+ synchronizer = s.crowd_synchronizer
15
+ synchronizer.local_record = s.record
16
+ synchronizer.crowd_record = s.crowd_record
17
+ synchronizer.sync_from_crowd
18
+ end
19
+ end
20
+ end
21
+
22
+ # Configuration for the crowd feature.
23
+ module Config
24
+
25
+ # How often should Crowd re-authorize (in seconds). Default is 0 (always re-authorize)
26
+ def crowd_auth_every(value = nil)
27
+ rw_config(:crowd_auth_every, value, 0)
28
+ end
29
+ alias_method :crowd_auth_every=, :crowd_auth_every
30
+
31
+ # Should a new local record be created for existing Crowd users with no
32
+ # matching local record?
33
+ # Default is true.
34
+ # Add this in your Session object if you need to disable auto-registration via crowd
35
+ def auto_register(value=true)
36
+ auto_register_value(value)
37
+ end
38
+
39
+ def auto_register_value(value=nil)
40
+ rw_config(:auto_register,value,true)
41
+ end
42
+ alias_method :auto_register=, :auto_register
43
+ end
44
+
45
+ module InstanceMethods
46
+
47
+ def initialize(*args)
48
+ super
49
+ @valid_crowd_user = {}
50
+ end
51
+
52
+ # Determines if the authenticated user is also a new registration.
53
+ # For use in the session controller to help direct the most appropriate action to follow.
54
+ def new_registration?
55
+ new_registration || !new_registration.nil?
56
+ end
57
+
58
+ def auto_register?
59
+ self.class.auto_register_value
60
+ end
61
+
62
+ def crowd_record
63
+ if @valid_crowd_user[:user_token] && !@valid_crowd_user.has_key?(:record)
64
+ @valid_crowd_user[:record] = crowd_client.find_user_by_token(@valid_crowd_user[:user_token])
65
+ end
66
+
67
+ @valid_crowd_user[:record]
68
+ end
69
+
70
+ def crowd_client
71
+ @crowd_client ||= klass.crowd_client
72
+ end
73
+
74
+ def crowd_synchronizer
75
+ @crowd_synchronizer ||= klass.crowd_synchronizer(crowd_client)
76
+ end
77
+
78
+ private
79
+
80
+ def authenticating_with_crowd?
81
+ klass.using_crowd? && (authenticated_by_crowd? || has_crowd_user_token? || has_crowd_credentials?)
82
+ end
83
+
84
+ # Use the Crowd to "log in" the user using the crowd.token_key
85
+ # cookie/parameter. If the token_key is valid and returns a valid Crowd
86
+ # user, the find_by_login_method is called to find the appropriate local
87
+ # user/record.
88
+ #
89
+ # If no *local* record is found and auto_register is enabled (default)
90
+ # then automatically create *local* record for them.
91
+ #
92
+ # This method enables a Crowd user to log in without having to explicity
93
+ # log in to this app. Once a Crowd user has authenticated with this app
94
+ # via this method, future requests usually use the Authlogic::Session
95
+ # module to persist/find users.
96
+ def persist_by_crowd
97
+ clear_crowd_auth_cache
98
+ return false unless has_crowd_user_token? && valid_crowd_user_token? && crowd_username
99
+ self.unauthorized_record = find_or_create_record_from_crowd
100
+ valid?
101
+ rescue SimpleCrowd::CrowdError => e
102
+ Rails.logger.warn "CROWD[#{__method__}]: Unexpected error. #{e}"
103
+ return false
104
+ end
105
+
106
+ # Validates the current record/user with Crowd. This validates the
107
+ # crowd.token_key cookie/parameter and/or explicit credentials.
108
+ #
109
+ # If a crowd.token_key exists and matches a previously authenticated
110
+ # token_key, this method will only verify the token with crowd if the
111
+ # last authorization was more than crowd_auth_every seconds ago (see
112
+ # the crowd_auth_every config option).
113
+ def validate_by_crowd
114
+ # Credentials trump a crowd.token_key.
115
+ # We don't even attempt to authenticated from the token key if
116
+ # credentials are present.
117
+ if has_crowd_credentials?
118
+ # HACK: Remove previous login/password errors since we are going to
119
+ # try to validate them with crowd
120
+ errors.instance_variable_get('@errors').delete(login_field.to_s)
121
+ errors.instance_variable_get('@errors').delete(password_field.to_s)
122
+
123
+ if valid_crowd_credentials?
124
+ self.attempted_record = find_or_create_record_from_crowd
125
+ unless self.attempted_record
126
+ errors.add(login_field, I18n.t('error_messages.login_not_found', :default => "is not valid"))
127
+ end
128
+ else
129
+ errors.add(login_field, I18n.t('error_messages.login_not_found', :default => "is not valid")) if !@valid_crowd_user[:username]
130
+ errors.add(password_field, I18n.t('error_messages.password_invalid', :default => "is not valid")) if @valid_crowd_user[:username]
131
+ end
132
+
133
+ elsif has_crowd_user_token?
134
+ unless valid_crowd_user_token? && valid_crowd_username?
135
+ errors.add_to_base(I18n.t('error_messages.crowd_invalid_user_token', :default => "invalid user token"))
136
+ end
137
+
138
+ elsif authenticated_by_crowd?
139
+ destroy
140
+ errors.add_to_base(I18n.t('error_messages.crowd_missing_using_token', :default => "missing user token"))
141
+ end
142
+
143
+ unless self.attempted_record.valid?
144
+ errors.add_to_base('record is not valid')
145
+ end
146
+
147
+ if errors.count == 0
148
+ # Set crowd.token_key cookie
149
+ save_crowd_cookie
150
+
151
+ # Cache crowd authorization to make future requests faster (if
152
+ # crowd_auth_every config is enabled)
153
+ cache_crowd_auth
154
+ end
155
+ rescue SimpleCrowd::CrowdError => e
156
+ Rails.logger.warn "CROWD[#{__method__}]: Unexpected error. #{e}"
157
+ errors.add_to_base("Crowd error: #{e}")
158
+ end
159
+
160
+ # Validate the crowd.token_key (if one exists)
161
+ # Uses simple_crowd to verify the user token on the configured crowd server.
162
+ # This only checks that the token is valid with Crowd. It does not check
163
+ # that the token belongs to a valid local user/record.
164
+ def valid_crowd_user_token?
165
+ unless @valid_crowd_user.has_key?(:user_token)
166
+ @valid_crowd_user[:user_token] = nil
167
+ user_token = crowd_user_token
168
+ if user_token
169
+ if crowd_client.is_valid_user_token?(user_token)
170
+ @valid_crowd_user[:user_token] = user_token
171
+ end
172
+ end
173
+ end
174
+ !!@valid_crowd_user[:user_token]
175
+ end
176
+
177
+ # Validate username/password using Crowd.
178
+ # Uses simple_crowd to verify credentials on the configured crowd server.
179
+ def valid_crowd_credentials?
180
+ login = send(login_field)
181
+ password = send("protected_#{password_field}")
182
+ return false unless login && password
183
+
184
+ unless @valid_crowd_user.has_key?(:credentials)
185
+ @valid_crowd_user[:user_token] = nil
186
+
187
+ # Authenticate using login/password
188
+ user_token = crowd_fetch {crowd_client.authenticate_user(login, password)}
189
+ if user_token
190
+ @valid_crowd_user[:user_token] = user_token
191
+ @valid_crowd_user[:username] = login
192
+ else
193
+ # See if the login exists
194
+ crecord = @valid_crowd_user[:record] = crowd_fetch {crowd_client.find_user_by_name(login)}
195
+ @valid_crowd_user[:username] = crecord ? crecord.username : nil
196
+ end
197
+
198
+ # Attempt to find user with crowd email field instead of principal name
199
+ if !@valid_crowd_user[:username] && login =~ Authlogic::Regex.email
200
+ crecord = @valid_crowd_user[:record] = crowd_fetch {crowd_client.find_user_by_email(login)}
201
+ if crecord
202
+ user_token = crowd_fetch {crowd_client.authenticate_user(crecord.username, password)}
203
+ @valid_crowd_user[:username] = crecord.username
204
+ @valid_crowd_user[:user_token] = user_token if user_token
205
+ end
206
+ end
207
+
208
+ @valid_crowd_user[:credentials] = !!@valid_crowd_user[:user_token]
209
+ end
210
+
211
+ @valid_crowd_user[:credentials]
212
+ end
213
+
214
+ # Validate the crowd username against the current record
215
+ def valid_crowd_username?
216
+ record_login = send(login_field) || (unauthorized_record && unauthorized_record.login)
217
+
218
+ # Use the last username if available to reduce crowd calls
219
+ if @valid_crowd_user[:user_token] && @valid_crowd_user[:user_token] == controller.session[:"crowd.last_user_token"]
220
+ crowd_login = controller.session[:"crowd.last_username"]
221
+ end
222
+ crowd_login = crowd_username unless crowd_login
223
+
224
+ crowd_login && crowd_login == record_login
225
+ end
226
+
227
+ def crowd_username
228
+ if @valid_crowd_user[:user_token] && !@valid_crowd_user.has_key?(:username)
229
+ crecord = crowd_record
230
+ @valid_crowd_user[:username] = crecord ? crecord.username : nil
231
+ end
232
+
233
+ @valid_crowd_user[:username]
234
+ end
235
+
236
+ def cache_crowd_auth
237
+ if @valid_crowd_user[:user_token]
238
+ controller.session[:"crowd.last_auth"] = Time.now
239
+ controller.session[:"crowd.last_user_token"] = @valid_crowd_user[:user_token].dup
240
+ controller.session[:"crowd.last_username"] = @valid_crowd_user[:username].dup if @valid_crowd_user[:username]
241
+ Rails.logger.debug "CROWD: Cached crowd authorization (#{controller.session[:"crowd.last_username"]}). Next authorization at #{Time.now + self.class.crowd_auth_every}." if self.class.crowd_auth_every.to_i > 0
242
+ else
243
+ clear_crowd_auth_cache
244
+ end
245
+ end
246
+
247
+ # Clear cached crowd information
248
+ def clear_crowd_auth_cache
249
+ controller.session.delete_if {|key, val| ["crowd.last_user_token", "crowd.last_auth", "crowd.last_username"].include?(key.to_s)}
250
+ end
251
+
252
+ def save_crowd_cookie
253
+ if @valid_crowd_user[:user_token] && @valid_crowd_user[:user_token] != crowd_user_token
254
+ controller.params.delete("crowd.token_key")
255
+ controller.cookies[:"crowd.token_key"] = {
256
+ :domain => crowd_cookie_info[:domain],
257
+ :secure => crowd_cookie_info[:secure],
258
+ :value => @valid_crowd_user[:user_token],
259
+ }
260
+ end
261
+ end
262
+
263
+ def destroy_crowd_cookie
264
+ controller.cookies.delete(:"crowd.token_key", :domain => crowd_cookie_info[:domain])
265
+ end
266
+
267
+ # When the crowd_auth_every config option is set and the user is logged
268
+ # in via crowd, validation can be skipped in certain cases (token_key
269
+ # matches last token_key and last authorization was less than
270
+ # crowd_auth_every seconds).
271
+ def needs_crowd_validation?
272
+ res = true
273
+ if !has_crowd_credentials? && authenticated_by_crowd? && self.class.crowd_auth_every.to_i > 0
274
+ last_user_token = controller.session[:"crowd.last_user_token"]
275
+ last_auth = controller.session[:"crowd.last_auth"]
276
+ if last_user_token
277
+ if !crowd_user_token
278
+ Rails.logger.debug "CROWD: Re-authorization required. Crowd token does not exist."
279
+ elsif last_user_token != crowd_user_token
280
+ Rails.logger.debug "CROWD: Re-authorization required. Crowd token does match cached token."
281
+ elsif last_auth && last_auth <= self.class.crowd_auth_every.ago
282
+ Rails.logger.debug "CROWD: Re-authorization required. Last authorization was at #{last_auth}."
283
+ elsif !last_auth
284
+ Rails.logger.debug "CROWD: Re-authorization required. Unable to determine last authorization time."
285
+ else
286
+ Rails.logger.debug "CROWD: Authenticating from cache. Next authorization at #{last_auth + self.class.crowd_auth_every}."
287
+ res = false
288
+ end
289
+ end
290
+ end
291
+ res
292
+ end
293
+
294
+ def find_or_create_record_from_crowd
295
+ return nil unless crowd_username
296
+ record = search_for_record(find_by_login_method, crowd_username)
297
+
298
+ if !record && auto_register?
299
+ synchronizer = crowd_synchronizer
300
+ synchronizer.crowd_record = crowd_record
301
+ record = synchronizer.create_record_from_crowd
302
+ self.new_registration if record
303
+ end
304
+
305
+ record
306
+ end
307
+
308
+ # Logout of crowd and remove the crowd cookie.
309
+ def logout_of_crowd
310
+ if crowd_user_token
311
+ # Send an invalidate call for single signout
312
+ # Apparently there is no way of knowing if this was successful or not.
313
+ begin
314
+ crowd_client.invalidate_user_token(crowd_user_token)
315
+ rescue SimpleCrowd::CrowdError => e
316
+ Rails.logger.debug "CROWD: logout_of_crowd #{e.message}"
317
+ end
318
+ end
319
+
320
+ controller.params.delete("crowd.token_key")
321
+ destroy_crowd_cookie
322
+ clear_crowd_auth_cache
323
+ true
324
+ end
325
+
326
+ def crowd_user_token
327
+ controller && (controller.params["crowd.token_key"] || controller.cookies[:"crowd.token_key"])
328
+ end
329
+
330
+ def authenticated_by_crowd?
331
+ !!controller.session[:"crowd.last_user_token"]
332
+ end
333
+
334
+ def has_crowd_user_token?
335
+ !!crowd_user_token
336
+ end
337
+
338
+ def has_crowd_credentials?
339
+ login_field && password_field && (!send(login_field).nil? || !send("protected_#{password_field}").nil?)
340
+ end
341
+
342
+ def crowd_cookie_info
343
+ @crowd_cookie_info ||= crowd_client.get_cookie_info
344
+ end
345
+
346
+ # Executes the given block, returning nil if a SimpleCrowd::CrowdError
347
+ # ocurrs.
348
+ def crowd_fetch
349
+ begin
350
+ yield
351
+ rescue SimpleCrowd::CrowdError => e
352
+ Rails.logger.debug "CROWD[#{caller[0][/`.*'/][1..-2]}]: #{e.message}"
353
+ nil
354
+ end
355
+ end
356
+ end
357
+ end
358
+ end