ruby-openid2 3.0.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 (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