ruby-openid2 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +136 -0
  4. data/CODE_OF_CONDUCT.md +84 -0
  5. data/CONTRIBUTING.md +54 -0
  6. data/LICENSE.txt +210 -0
  7. data/README.md +81 -0
  8. data/SECURITY.md +15 -0
  9. data/lib/hmac/hmac.rb +110 -0
  10. data/lib/hmac/sha1.rb +11 -0
  11. data/lib/hmac/sha2.rb +25 -0
  12. data/lib/openid/association.rb +246 -0
  13. data/lib/openid/consumer/associationmanager.rb +354 -0
  14. data/lib/openid/consumer/checkid_request.rb +179 -0
  15. data/lib/openid/consumer/discovery.rb +516 -0
  16. data/lib/openid/consumer/discovery_manager.rb +144 -0
  17. data/lib/openid/consumer/html_parse.rb +142 -0
  18. data/lib/openid/consumer/idres.rb +513 -0
  19. data/lib/openid/consumer/responses.rb +147 -0
  20. data/lib/openid/consumer/session.rb +36 -0
  21. data/lib/openid/consumer.rb +406 -0
  22. data/lib/openid/cryptutil.rb +112 -0
  23. data/lib/openid/dh.rb +84 -0
  24. data/lib/openid/extension.rb +38 -0
  25. data/lib/openid/extensions/ax.rb +552 -0
  26. data/lib/openid/extensions/oauth.rb +88 -0
  27. data/lib/openid/extensions/pape.rb +170 -0
  28. data/lib/openid/extensions/sreg.rb +268 -0
  29. data/lib/openid/extensions/ui.rb +49 -0
  30. data/lib/openid/fetchers.rb +277 -0
  31. data/lib/openid/kvform.rb +113 -0
  32. data/lib/openid/kvpost.rb +62 -0
  33. data/lib/openid/message.rb +555 -0
  34. data/lib/openid/protocolerror.rb +7 -0
  35. data/lib/openid/server.rb +1571 -0
  36. data/lib/openid/store/filesystem.rb +260 -0
  37. data/lib/openid/store/interface.rb +73 -0
  38. data/lib/openid/store/memcache.rb +109 -0
  39. data/lib/openid/store/memory.rb +79 -0
  40. data/lib/openid/store/nonce.rb +72 -0
  41. data/lib/openid/trustroot.rb +597 -0
  42. data/lib/openid/urinorm.rb +72 -0
  43. data/lib/openid/util.rb +119 -0
  44. data/lib/openid/version.rb +5 -0
  45. data/lib/openid/yadis/accept.rb +141 -0
  46. data/lib/openid/yadis/constants.rb +16 -0
  47. data/lib/openid/yadis/discovery.rb +151 -0
  48. data/lib/openid/yadis/filters.rb +192 -0
  49. data/lib/openid/yadis/htmltokenizer.rb +290 -0
  50. data/lib/openid/yadis/parsehtml.rb +50 -0
  51. data/lib/openid/yadis/services.rb +44 -0
  52. data/lib/openid/yadis/xrds.rb +160 -0
  53. data/lib/openid/yadis/xri.rb +86 -0
  54. data/lib/openid/yadis/xrires.rb +87 -0
  55. data/lib/openid.rb +27 -0
  56. data/lib/ruby-openid.rb +1 -0
  57. data.tar.gz.sig +0 -0
  58. metadata +331 -0
  59. metadata.gz.sig +0 -0
@@ -0,0 +1,516 @@
1
+ # Functions to discover OpenID endpoints from identifiers.
2
+
3
+ require "uri"
4
+ require_relative "../util"
5
+ require_relative "../fetchers"
6
+ require_relative "../urinorm"
7
+ require_relative "../message"
8
+ require_relative "../yadis/discovery"
9
+ require_relative "../yadis/xrds"
10
+ require_relative "../yadis/xri"
11
+ require_relative "../yadis/services"
12
+ require_relative "../yadis/filters"
13
+ require_relative "../consumer/html_parse"
14
+ require_relative "../yadis/xrires"
15
+
16
+ module OpenID
17
+ OPENID_1_0_NS = "http://openid.net/xmlns/1.0"
18
+ OPENID_IDP_2_0_TYPE = "http://specs.openid.net/auth/2.0/server"
19
+ OPENID_2_0_TYPE = "http://specs.openid.net/auth/2.0/signon"
20
+ OPENID_1_1_TYPE = "http://openid.net/signon/1.1"
21
+ OPENID_1_0_TYPE = "http://openid.net/signon/1.0"
22
+
23
+ OPENID_1_0_MESSAGE_NS = OPENID1_NS
24
+ OPENID_2_0_MESSAGE_NS = OPENID2_NS
25
+
26
+ # Object representing an OpenID service endpoint.
27
+ class OpenIDServiceEndpoint
28
+ # OpenID service type URIs, listed in order of preference. The
29
+ # ordering of this list affects yadis and XRI service discovery.
30
+ OPENID_TYPE_URIS = [
31
+ OPENID_IDP_2_0_TYPE,
32
+
33
+ OPENID_2_0_TYPE,
34
+ OPENID_1_1_TYPE,
35
+ OPENID_1_0_TYPE,
36
+ ]
37
+
38
+ # the verified identifier.
39
+ attr_accessor :claimed_id
40
+
41
+ # For XRI, the persistent identifier.
42
+ attr_accessor :canonical_id
43
+
44
+ attr_accessor :server_url, :type_uris, :local_id, :used_yadis
45
+
46
+ def initialize
47
+ @claimed_id = nil
48
+ @server_url = nil
49
+ @type_uris = []
50
+ @local_id = nil
51
+ @canonical_id = nil
52
+ @used_yadis = false # whether this came from an XRDS
53
+ @display_identifier = nil
54
+ end
55
+
56
+ def display_identifier
57
+ return @display_identifier if @display_identifier
58
+
59
+ return @claimed_id if @claimed_id.nil?
60
+
61
+ begin
62
+ parsed_identifier = URI.parse(@claimed_id)
63
+ rescue URI::InvalidURIError
64
+ raise ProtocolError, "Claimed identifier #{claimed_id} is not a valid URI"
65
+ end
66
+
67
+ return @claimed_id unless parsed_identifier.fragment
68
+
69
+ disp = parsed_identifier
70
+ disp.fragment = nil
71
+
72
+ disp.to_s
73
+ end
74
+
75
+ attr_writer :display_identifier
76
+
77
+ def uses_extension(extension_uri)
78
+ @type_uris.member?(extension_uri)
79
+ end
80
+
81
+ def preferred_namespace
82
+ if @type_uris.member?(OPENID_IDP_2_0_TYPE) or
83
+ @type_uris.member?(OPENID_2_0_TYPE)
84
+ OPENID_2_0_MESSAGE_NS
85
+ else
86
+ OPENID_1_0_MESSAGE_NS
87
+ end
88
+ end
89
+
90
+ def supports_type(type_uri)
91
+ # Does this endpoint support this type?
92
+ #
93
+ # I consider C{/server} endpoints to implicitly support C{/signon}.
94
+ (
95
+ @type_uris.member?(type_uri) or
96
+ (type_uri == OPENID_2_0_TYPE and is_op_identifier)
97
+ )
98
+ end
99
+
100
+ def compatibility_mode
101
+ preferred_namespace != OPENID_2_0_MESSAGE_NS
102
+ end
103
+
104
+ def is_op_identifier
105
+ @type_uris.member?(OPENID_IDP_2_0_TYPE)
106
+ end
107
+
108
+ def parse_service(yadis_url, uri, type_uris, service_element)
109
+ # Set the state of this object based on the contents of the
110
+ # service element.
111
+ @type_uris = type_uris
112
+ @server_url = uri
113
+ @used_yadis = true
114
+
115
+ return if is_op_identifier
116
+
117
+ # XXX: This has crappy implications for Service elements that
118
+ # contain both 'server' and 'signon' Types. But that's a
119
+ # pathological configuration anyway, so I don't think I care.
120
+ @local_id = OpenID.find_op_local_identifier(
121
+ service_element,
122
+ @type_uris,
123
+ )
124
+ @claimed_id = yadis_url
125
+ end
126
+
127
+ def get_local_id
128
+ # Return the identifier that should be sent as the
129
+ # openid.identity parameter to the server.
130
+ if @local_id.nil? and @canonical_id.nil?
131
+ @claimed_id
132
+ else
133
+ (@local_id or @canonical_id)
134
+ end
135
+ end
136
+
137
+ def to_session_value
138
+ Hash[*instance_variables.flat_map { |name| [name, instance_variable_get(name)] }]
139
+ end
140
+
141
+ def ==(other)
142
+ to_session_value == other.to_session_value
143
+ end
144
+
145
+ def self.from_session_value(value)
146
+ return value unless value.is_a?(Hash)
147
+
148
+ new.tap do |endpoint|
149
+ value.each do |name, val|
150
+ endpoint.instance_variable_set(name, val)
151
+ end
152
+ end
153
+ end
154
+
155
+ def self.from_basic_service_endpoint(endpoint)
156
+ # Create a new instance of this class from the endpoint object
157
+ # passed in.
158
+ #
159
+ # @return: nil or OpenIDServiceEndpoint for this endpoint object"""
160
+
161
+ type_uris = endpoint.match_types(OPENID_TYPE_URIS)
162
+
163
+ # If any Type URIs match and there is an endpoint URI specified,
164
+ # then this is an OpenID endpoint
165
+ if (!type_uris.nil? and !type_uris.empty?) and !endpoint.uri.nil?
166
+ openid_endpoint = new
167
+ openid_endpoint.parse_service(
168
+ endpoint.yadis_url,
169
+ endpoint.uri,
170
+ endpoint.type_uris,
171
+ endpoint.service_element,
172
+ )
173
+ else
174
+ openid_endpoint = nil
175
+ end
176
+
177
+ openid_endpoint
178
+ end
179
+
180
+ def self.from_html(uri, html)
181
+ # Parse the given document as HTML looking for an OpenID <link
182
+ # rel=...>
183
+ #
184
+ # @rtype: [OpenIDServiceEndpoint]
185
+
186
+ discovery_types = [
187
+ [OPENID_2_0_TYPE, "openid2.provider", "openid2.local_id"],
188
+ [OPENID_1_1_TYPE, "openid.server", "openid.delegate"],
189
+ ]
190
+
191
+ link_attrs = OpenID.parse_link_attrs(html)
192
+ services = []
193
+ discovery_types.each do |type_uri, op_endpoint_rel, local_id_rel|
194
+ op_endpoint_url = OpenID.find_first_href(link_attrs, op_endpoint_rel)
195
+
196
+ next unless op_endpoint_url
197
+
198
+ service = new
199
+ service.claimed_id = uri
200
+ service.local_id = OpenID.find_first_href(link_attrs, local_id_rel)
201
+ service.server_url = op_endpoint_url
202
+ service.type_uris = [type_uri]
203
+
204
+ services << service
205
+ end
206
+
207
+ services
208
+ end
209
+
210
+ def self.from_xrds(uri, xrds)
211
+ # Parse the given document as XRDS looking for OpenID services.
212
+ #
213
+ # @rtype: [OpenIDServiceEndpoint]
214
+ #
215
+ # @raises L{XRDSError}: When the XRDS does not parse.
216
+ Yadis.apply_filter(uri, xrds, self)
217
+ end
218
+
219
+ def self.from_discovery_result(discovery_result)
220
+ # Create endpoints from a DiscoveryResult.
221
+ #
222
+ # @type discoveryResult: L{DiscoveryResult}
223
+ #
224
+ # @rtype: list of L{OpenIDServiceEndpoint}
225
+ #
226
+ # @raises L{XRDSError}: When the XRDS does not parse.
227
+ meth = if discovery_result.is_xrds
228
+ method(:from_xrds)
229
+ else
230
+ method(:from_html)
231
+ end
232
+
233
+ meth.call(
234
+ discovery_result.normalized_uri,
235
+ discovery_result.response_text,
236
+ )
237
+ end
238
+
239
+ def self.from_op_endpoint_url(op_endpoint_url)
240
+ # Construct an OP-Identifier OpenIDServiceEndpoint object for
241
+ # a given OP Endpoint URL
242
+ #
243
+ # @param op_endpoint_url: The URL of the endpoint
244
+ # @rtype: OpenIDServiceEndpoint
245
+ service = new
246
+ service.server_url = op_endpoint_url
247
+ service.type_uris = [OPENID_IDP_2_0_TYPE]
248
+ service
249
+ end
250
+
251
+ def to_s
252
+ format(
253
+ "<%s server_url=%s claimed_id=%s " +
254
+ "local_id=%s canonical_id=%s used_yadis=%s>",
255
+ self.class,
256
+ @server_url,
257
+ @claimed_id,
258
+ @local_id,
259
+ @canonical_id,
260
+ @used_yadis,
261
+ )
262
+ end
263
+ end
264
+
265
+ def self.find_op_local_identifier(service_element, type_uris)
266
+ # Find the OP-Local Identifier for this xrd:Service element.
267
+ #
268
+ # This considers openid:Delegate to be a synonym for xrd:LocalID
269
+ # if both OpenID 1.X and OpenID 2.0 types are present. If only
270
+ # OpenID 1.X is present, it returns the value of
271
+ # openid:Delegate. If only OpenID 2.0 is present, it returns the
272
+ # value of xrd:LocalID. If there is more than one LocalID tag and
273
+ # the values are different, it raises a DiscoveryFailure. This is
274
+ # also triggered when the xrd:LocalID and openid:Delegate tags are
275
+ # different.
276
+
277
+ # XXX: Test this function on its own!
278
+
279
+ # Build the list of tags that could contain the OP-Local
280
+ # Identifier
281
+ local_id_tags = []
282
+ if type_uris.member?(OPENID_1_1_TYPE) or
283
+ type_uris.member?(OPENID_1_0_TYPE)
284
+ # local_id_tags << Yadis::nsTag(OPENID_1_0_NS, 'openid', 'Delegate')
285
+ service_element.add_namespace("openid", OPENID_1_0_NS)
286
+ local_id_tags << "openid:Delegate"
287
+ end
288
+
289
+ if type_uris.member?(OPENID_2_0_TYPE)
290
+ # local_id_tags.append(Yadis::nsTag(XRD_NS_2_0, 'xrd', 'LocalID'))
291
+ service_element.add_namespace("xrd", Yadis::XRD_NS_2_0)
292
+ local_id_tags << "xrd:LocalID"
293
+ end
294
+
295
+ # Walk through all the matching tags and make sure that they all
296
+ # have the same value
297
+ local_id = nil
298
+ local_id_tags.each do |local_id_tag|
299
+ service_element.each_element(local_id_tag) do |local_id_element|
300
+ if local_id.nil?
301
+ local_id = local_id_element.text
302
+ elsif local_id != local_id_element.text
303
+ format = "More than one %s tag found in one service element"
304
+ message = format(format, local_id_tag)
305
+ raise DiscoveryFailure.new(message, nil)
306
+ end
307
+ end
308
+ end
309
+
310
+ local_id
311
+ end
312
+
313
+ def self.normalize_xri(xri)
314
+ # Normalize an XRI, stripping its scheme if present
315
+ m = %r{^xri://(.*)}.match(xri)
316
+ xri = m[1] if m
317
+ xri
318
+ end
319
+
320
+ def self.normalize_url(url)
321
+ # Normalize a URL, converting normalization failures to
322
+ # DiscoveryFailure
323
+
324
+ normalized = URINorm.urinorm(url)
325
+ rescue URI::Error => e
326
+ raise DiscoveryFailure.new("Error normalizing #{url}: #{e.message}", nil)
327
+ else
328
+ defragged = URI.parse(normalized)
329
+ defragged.fragment = nil
330
+ defragged.normalize.to_s
331
+ end
332
+
333
+ def self.best_matching_service(service, preferred_types)
334
+ # Return the index of the first matching type, or something higher
335
+ # if no type matches.
336
+ #
337
+ # This provides an ordering in which service elements that contain
338
+ # a type that comes earlier in the preferred types list come
339
+ # before service elements that come later. If a service element
340
+ # has more than one type, the most preferred one wins.
341
+ preferred_types.each_with_index do |value, index|
342
+ return index if service.type_uris.member?(value)
343
+ end
344
+
345
+ preferred_types.length
346
+ end
347
+
348
+ def self.arrange_by_type(service_list, preferred_types)
349
+ # Rearrange service_list in a new list so services are ordered by
350
+ # types listed in preferred_types. Return the new list.
351
+
352
+ # Build a list with the service elements in tuples whose
353
+ # comparison will prefer the one with the best matching service
354
+ prio_services = []
355
+
356
+ service_list.each_with_index do |s, index|
357
+ prio_services << [best_matching_service(s, preferred_types), index, s]
358
+ end
359
+
360
+ prio_services.sort!
361
+
362
+ # Now that the services are sorted by priority, remove the sort
363
+ # keys from the list.
364
+ (0...prio_services.length).each do |i|
365
+ prio_services[i] = prio_services[i][2]
366
+ end
367
+
368
+ prio_services
369
+ end
370
+
371
+ def self.get_op_or_user_services(openid_services)
372
+ # Extract OP Identifier services. If none found, return the rest,
373
+ # sorted with most preferred first according to
374
+ # OpenIDServiceEndpoint.openid_type_uris.
375
+ #
376
+ # openid_services is a list of OpenIDServiceEndpoint objects.
377
+ #
378
+ # Returns a list of OpenIDServiceEndpoint objects.
379
+
380
+ op_services = arrange_by_type(openid_services, [OPENID_IDP_2_0_TYPE])
381
+
382
+ openid_services = arrange_by_type(
383
+ openid_services,
384
+ OpenIDServiceEndpoint::OPENID_TYPE_URIS,
385
+ )
386
+
387
+ if !op_services.empty?
388
+ op_services
389
+ else
390
+ openid_services
391
+ end
392
+ end
393
+
394
+ def self.discover_yadis(uri)
395
+ # Discover OpenID services for a URI. Tries Yadis and falls back
396
+ # on old-style <link rel='...'> discovery if Yadis fails.
397
+ #
398
+ # @param uri: normalized identity URL
399
+ # @type uri: str
400
+ #
401
+ # @return: (claimed_id, services)
402
+ # @rtype: (str, list(OpenIDServiceEndpoint))
403
+ #
404
+ # @raises DiscoveryFailure: when discovery fails.
405
+
406
+ # Might raise a yadis.discover.DiscoveryFailure if no document
407
+ # came back for that URI at all. I don't think falling back to
408
+ # OpenID 1.0 discovery on the same URL will help, so don't bother
409
+ # to catch it.
410
+ response = Yadis.discover(uri)
411
+
412
+ yadis_url = response.normalized_uri
413
+ body = response.response_text
414
+
415
+ begin
416
+ openid_services = OpenIDServiceEndpoint.from_xrds(yadis_url, body)
417
+ rescue Yadis::XRDSError
418
+ # Does not parse as a Yadis XRDS file
419
+ openid_services = []
420
+ end
421
+
422
+ if openid_services.empty?
423
+ # Either not an XRDS or there are no OpenID services.
424
+
425
+ if response.is_xrds
426
+ # if we got the Yadis content-type or followed the Yadis
427
+ # header, re-fetch the document without following the Yadis
428
+ # header, with no Accept header.
429
+ return discover_no_yadis(uri)
430
+ end
431
+
432
+ # Try to parse the response as HTML.
433
+ # <link rel="...">
434
+ openid_services = OpenIDServiceEndpoint.from_html(yadis_url, body)
435
+ end
436
+
437
+ [yadis_url, get_op_or_user_services(openid_services)]
438
+ end
439
+
440
+ def self.discover_xri(iname)
441
+ endpoints = []
442
+ iname = normalize_xri(iname)
443
+
444
+ begin
445
+ canonical_id, services = Yadis::XRI::ProxyResolver.new.query(iname)
446
+
447
+ raise Yadis::XRDSError.new(format("No CanonicalID found for XRI %s", iname)) if canonical_id.nil?
448
+
449
+ flt = Yadis.make_filter(OpenIDServiceEndpoint)
450
+
451
+ services.each do |service_element|
452
+ endpoints += flt.get_service_endpoints(iname, service_element)
453
+ end
454
+ rescue Yadis::XRDSError, Yadis::XRI::XRIHTTPError => e
455
+ Util.log("xrds error on " + iname + ": " + e.to_s)
456
+ end
457
+
458
+ endpoints.each do |endpoint|
459
+ # Is there a way to pass this through the filter to the endpoint
460
+ # constructor instead of tacking it on after?
461
+ endpoint.canonical_id = canonical_id
462
+ endpoint.claimed_id = canonical_id
463
+ endpoint.display_identifier = iname
464
+ end
465
+
466
+ # FIXME: returned xri should probably be in some normal form
467
+ [iname, get_op_or_user_services(endpoints)]
468
+ end
469
+
470
+ def self.discover_no_yadis(uri)
471
+ http_resp = OpenID.fetch(uri)
472
+ if http_resp.code != "200" and http_resp.code != "206"
473
+ raise DiscoveryFailure.new(
474
+ 'HTTP Response status from identity URL host is not "200". ' \
475
+ "Got status #{http_resp.code.inspect}",
476
+ http_resp,
477
+ )
478
+ end
479
+
480
+ claimed_id = http_resp.final_url
481
+ openid_services = OpenIDServiceEndpoint.from_html(
482
+ claimed_id, http_resp.body
483
+ )
484
+ [claimed_id, openid_services]
485
+ end
486
+
487
+ def self.discover_uri(uri)
488
+ # Hack to work around URI parsing for URls with *no* scheme.
489
+ uri = "http://" + uri if uri.index("://").nil?
490
+
491
+ begin
492
+ parsed = URI.parse(uri)
493
+ rescue URI::InvalidURIError => e
494
+ raise DiscoveryFailure.new("URI is not valid: #{e.message}", nil)
495
+ end
496
+
497
+ if !parsed.scheme.nil? and !parsed.scheme.empty? && !%w[http https].member?(parsed.scheme)
498
+ raise DiscoveryFailure.new(
499
+ "URI scheme #{parsed.scheme} is not HTTP or HTTPS", nil
500
+ )
501
+ end
502
+
503
+ uri = normalize_url(uri)
504
+ claimed_id, openid_services = discover_yadis(uri)
505
+ claimed_id = normalize_url(claimed_id)
506
+ [claimed_id, openid_services]
507
+ end
508
+
509
+ def self.discover(identifier)
510
+ if Yadis::XRI.identifier_scheme(identifier) == :xri
511
+ discover_xri(identifier)
512
+ else
513
+ discover_uri(identifier)
514
+ end
515
+ end
516
+ end
@@ -0,0 +1,144 @@
1
+ module OpenID
2
+ class Consumer
3
+ # A set of discovered services, for tracking which providers have
4
+ # been attempted for an OpenID identifier
5
+ class DiscoveredServices
6
+ attr_reader :current
7
+
8
+ def initialize(starting_url, yadis_url, services)
9
+ @starting_url = starting_url
10
+ @yadis_url = yadis_url
11
+ @services = services.dup
12
+ @current = nil
13
+ end
14
+
15
+ def next
16
+ @current = @services.shift
17
+ end
18
+
19
+ def for_url?(url)
20
+ [@starting_url, @yadis_url].member?(url)
21
+ end
22
+
23
+ def started?
24
+ !@current.nil?
25
+ end
26
+
27
+ def empty?
28
+ @services.empty?
29
+ end
30
+
31
+ def to_session_value
32
+ services = @services.map { |s| s.respond_to?(:to_session_value) ? s.to_session_value : s }
33
+ current_val = @current.respond_to?(:to_session_value) ? @current.to_session_value : @current
34
+
35
+ {
36
+ "starting_url" => @starting_url,
37
+ "yadis_url" => @yadis_url,
38
+ "services" => services,
39
+ "current" => current_val,
40
+ }
41
+ end
42
+
43
+ def ==(other)
44
+ to_session_value == other.to_session_value
45
+ end
46
+
47
+ def self.from_session_value(value)
48
+ return value unless value.is_a?(Hash)
49
+
50
+ services = value["services"].map { |s| OpenID::OpenIDServiceEndpoint.from_session_value(s) }
51
+ current = OpenID::OpenIDServiceEndpoint.from_session_value(value["current"])
52
+
53
+ obj = new(value["starting_url"], value["yadis_url"], services)
54
+ obj.instance_variable_set(:@current, current)
55
+ obj
56
+ end
57
+ end
58
+
59
+ # Manages calling discovery and tracking which endpoints have
60
+ # already been attempted.
61
+ class DiscoveryManager
62
+ def initialize(session, url, session_key_suffix = nil)
63
+ @url = url
64
+
65
+ @session = OpenID::Consumer::Session.new(session, DiscoveredServices)
66
+ @session_key_suffix = session_key_suffix || "auth"
67
+ end
68
+
69
+ def get_next_service
70
+ manager = get_manager
71
+ if !manager.nil? && manager.empty?
72
+ destroy_manager
73
+ manager = nil
74
+ end
75
+
76
+ if manager.nil?
77
+ yadis_url, services = yield @url
78
+ manager = create_manager(yadis_url, services)
79
+ end
80
+
81
+ if !manager.nil?
82
+ service = manager.next
83
+ store(manager)
84
+ else
85
+ service = nil
86
+ end
87
+
88
+ service
89
+ end
90
+
91
+ def cleanup(force = false)
92
+ manager = get_manager(force)
93
+ if !manager.nil?
94
+ service = manager.current
95
+ destroy_manager(force)
96
+ else
97
+ service = nil
98
+ end
99
+ service
100
+ end
101
+
102
+ protected
103
+
104
+ def get_manager(force = false)
105
+ manager = load
106
+ return manager if force || manager.nil? || manager.for_url?(@url)
107
+
108
+ nil
109
+ end
110
+
111
+ def create_manager(yadis_url, services)
112
+ manager = get_manager
113
+ raise StandardError, "There is already a manager for #{yadis_url}" unless manager.nil?
114
+ return if services.empty?
115
+
116
+ manager = DiscoveredServices.new(@url, yadis_url, services)
117
+ store(manager)
118
+ manager
119
+ end
120
+
121
+ def destroy_manager(force = false)
122
+ return if get_manager(force).nil?
123
+
124
+ destroy!
125
+ end
126
+
127
+ def session_key
128
+ "OpenID::Consumer::DiscoveredServices::" + @session_key_suffix
129
+ end
130
+
131
+ def store(manager)
132
+ @session[session_key] = manager
133
+ end
134
+
135
+ def load
136
+ @session[session_key]
137
+ end
138
+
139
+ def destroy!
140
+ @session[session_key] = nil
141
+ end
142
+ end
143
+ end
144
+ end