authlogic_crowd 0.2.4 → 0.3.0.pre.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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