C0 code coverage information

Generated on Fri Jul 11 15:55:30 -0700 2008 with rcov 0.7.0


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

Generated using the rcov code coverage analysis tool for Ruby version 0.7.0.

Valid XHTML 1.0! Valid CSS!