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/idres.rb 523 391
88.9% 
85.4% 
  1 require "openid/message"
  2 require "openid/protocolerror"
  3 require "openid/kvpost"
  4 require "openid/consumer/discovery"
  5 require "openid/urinorm"
  6 
  7 module OpenID
  8   class TypeURIMismatch < ProtocolError
  9     attr_reader :type_uri, :endpoint
 10 
 11     def initialize(type_uri, endpoint)
 12       @type_uri = type_uri
 13       @endpoint = endpoint
 14     end
 15   end
 16 
 17   class Consumer
 18     @openid1_return_to_nonce_name = 'rp_nonce'
 19     @openid1_return_to_claimed_id_name = 'openid1_claimed_id'
 20 
 21     # Set the name of the query parameter that this library will use
 22     # to thread a nonce through an OpenID 1 transaction. It will be
 23     # appended to the return_to URL.
 24     def self.openid1_return_to_nonce_name=(query_arg_name)
 25       @openid1_return_to_nonce_name = query_arg_name
 26     end
 27 
 28     # See openid1_return_to_nonce_name= documentation
 29     def self.openid1_return_to_nonce_name
 30       @openid1_return_to_nonce_name
 31     end
 32 
 33     # Set the name of the query parameter that this library will use
 34     # to thread the requested URL through an OpenID 1 transaction (for
 35     # use when verifying discovered information). It will be appended
 36     # to the return_to URL.
 37     def self.openid1_return_to_claimed_id_name=(query_arg_name)
 38       @openid1_return_to_claimed_id_name = query_arg_name
 39     end
 40 
 41     # See openid1_return_to_claimed_id_name=
 42     def self.openid1_return_to_claimed_id_name
 43       @openid1_return_to_claimed_id_name
 44     end
 45 
 46     # Handles an openid.mode=id_res response. This object is
 47     # instantiated and used by the Consumer.
 48     class IdResHandler
 49       attr_reader :endpoint, :message
 50 
 51       def initialize(message, current_url, store=nil, endpoint=nil)
 52         @store = store # Fer the nonce and invalidate_handle
 53         @message = message
 54         @endpoint = endpoint
 55         @current_url = current_url
 56         @signed_list = nil
 57 
 58         # Start the verification process
 59         id_res
 60       end
 61 
 62       def signed_fields
 63         signed_list.map {|x| 'openid.' + x}
 64       end
 65 
 66       protected
 67 
 68       # This method will raise ProtocolError unless the request is a
 69       # valid id_res response. Once it has been verified, the methods
 70       # 'endpoint', 'message', and 'signed_fields' contain the
 71       # verified information.
 72       def id_res
 73         check_for_fields
 74         verify_return_to
 75         verify_discovery_results
 76         check_signature
 77         check_nonce
 78       end
 79 
 80       def server_url
 81         @endpoint.nil? ? nil : @endpoint.server_url
 82       end
 83 
 84       def openid_namespace
 85         @message.get_openid_namespace
 86       end
 87 
 88       def fetch(field, default=NO_DEFAULT)
 89         @message.get_arg(OPENID_NS, field, default)
 90       end
 91 
 92       def signed_list
 93         if @signed_list.nil?
 94           signed_list_str = fetch('signed', nil)
 95           if signed_list_str.nil?
 96             raise ProtocolError, 'Response missing signed list'
 97           end
 98 
 99           @signed_list = signed_list_str.split(',', -1)
100         end
101         @signed_list
102       end
103 
104       def check_for_fields
105         # XXX: if a field is missing, we should not have to explicitly
106         # check that it's present, just make sure that the fields are
107         # actually being used by the rest of the code in
108         # tests. Although, which fields are signed does need to be
109         # checked somewhere.
110         basic_fields = ['return_to', 'assoc_handle', 'sig', 'signed']
111         basic_sig_fields = ['return_to', 'identity']
112 
113         case openid_namespace
114         when OPENID2_NS
115           require_fields = basic_fields + ['op_endpoint']
116           require_sigs = basic_sig_fields +
117             ['response_nonce', 'claimed_id', 'assoc_handle',]
118         when OPENID1_NS
119           require_fields = basic_fields + ['identity']
120           require_sigs = basic_sig_fields
121         else
122           raise RuntimeError, "check_for_fields doesn't know about "\
123                               "namespace #{openid_namespace.inspect}"
124         end
125 
126         require_fields.each do |field|
127           if !@message.has_key?(OPENID_NS, field)
128             raise ProtocolError, "Missing required field #{field}"
129           end
130         end
131 
132         require_sigs.each do |field|
133           # Field is present and not in signed list
134           if @message.has_key?(OPENID_NS, field) && !signed_list.member?(field)
135             raise ProtocolError, "#{field.inspect} not signed"
136           end
137         end
138       end
139 
140       def verify_return_to
141         begin
142           msg_return_to = URI.parse(URINorm::urinorm(fetch('return_to')))
143         rescue URI::InvalidURIError
144           raise ProtocolError, ("return_to is not a valid URI")
145         end
146 
147         verify_return_to_args(msg_return_to)
148         if !@current_url.nil?
149           verify_return_to_base(msg_return_to)
150         end
151       end
152 
153       def verify_return_to_args(msg_return_to)
154         return_to_parsed_query = {}
155         if !msg_return_to.query.nil?
156           CGI.parse(msg_return_to.query).each_pair do |k, vs|
157             return_to_parsed_query[k] = vs[0]
158           end
159         end
160         query = @message.to_post_args
161         return_to_parsed_query.each_pair do |rt_key, rt_val|
162           msg_val = query[rt_key]
163           if msg_val.nil?
164             raise ProtocolError, "Message missing return_to argument '#{rt_key}'"
165           elsif msg_val != rt_val
166             raise ProtocolError, ("Parameter '#{rt_key}' value "\
167                                   "#{msg_val.inspect} does not match "\
168                                   "return_to's value #{rt_val.inspect}")
169           end
170         end
171         @message.get_args(BARE_NS).each_pair do |bare_key, bare_val|
172           rt_val = return_to_parsed_query[bare_key]
173           if not return_to_parsed_query.has_key? bare_key
174             # This may be caused by your web framework throwing extra
175             # entries in to your parameters hash that were not GET or
176             # POST parameters.  For example, Rails has been known to
177             # add "controller" and "action" keys; another server adds
178             # at least a "format" key.
179             raise ProtocolError, ("Unexpected parameter (not on return_to): "\
180                                   "'#{bare_key}'=#{rt_val.inspect})")
181           end
182           if rt_val != bare_val
183             raise ProtocolError, ("Parameter '#{bare_key}' value "\
184                                   "#{bare_val.inspect} does not match "\
185                                   "return_to's value #{rt_val.inspect}")
186           end
187         end
188       end
189 
190       def verify_return_to_base(msg_return_to)
191         begin
192           app_parsed = URI.parse(URINorm::urinorm(@current_url))
193         rescue URI::InvalidURIError
194           raise ProtocolError, "current_url is not a valid URI: #{@current_url}"
195         end
196 
197         [:scheme, :host, :port, :path].each do |meth|
198           if msg_return_to.send(meth) != app_parsed.send(meth)
199             raise ProtocolError, "return_to #{meth.to_s} does not match"
200           end
201         end
202       end
203 
204       # Raises ProtocolError if the signature is bad
205       def check_signature
206         if @store.nil?
207           assoc = nil
208         else
209           assoc = @store.get_association(server_url, fetch('assoc_handle'))
210         end
211 
212         if assoc.nil?
213           check_auth
214         else
215           if assoc.expires_in <= 0
216             # XXX: It might be a good idea sometimes to re-start the
217             # authentication with a new association. Doing it
218             # automatically opens the possibility for
219             # denial-of-service by a server that just returns expired
220             # associations (or really short-lived associations)
221             raise ProtocolError, "Association with #{server_url} expired"
222           elsif !assoc.check_message_signature(@message)
223             raise ProtocolError, "Bad signature in response from #{server_url}"
224           end
225         end
226       end
227 
228       def check_auth
229         Util.log("Using 'check_authentication' with #{server_url}")
230         begin
231           request = create_check_auth_request
232         rescue Message::KeyNotFound => why
233           raise ProtocolError, "Could not generate 'check_authentication' "\
234                                "request: #{why.message}"
235         end
236 
237         response = OpenID.make_kv_post(request, server_url)
238 
239         process_check_auth_response(response)
240       end
241 
242       def create_check_auth_request
243         signed_list = @message.get_arg(OPENID_NS, 'signed', NO_DEFAULT).split(',')
244 
245         # check that we got all the signed arguments
246         signed_list.each {|k|
247           @message.get_aliased_arg(k, NO_DEFAULT)
248         }
249 
250         ca_message = @message.copy
251         ca_message.set_arg(OPENID_NS, 'mode', 'check_authentication')
252 
253         return ca_message
254       end
255 
256       # Process the response message from a check_authentication
257       # request, invalidating associations if requested.
258       def process_check_auth_response(response)
259         is_valid = response.get_arg(OPENID_NS, 'is_valid', 'false')
260 
261         invalidate_handle = response.get_arg(OPENID_NS, 'invalidate_handle')
262         if !invalidate_handle.nil?
263           Util.log("Received 'invalidate_handle' from server #{server_url}")
264           if @store.nil?
265             Util.log('Unexpectedly got "invalidate_handle" without a store!')
266           else
267             @store.remove_association(server_url, invalidate_handle)
268           end
269         end
270 
271         if is_valid != 'true'
272           raise ProtocolError, ("Server #{server_url} responds that the "\
273                                 "'check_authentication' call is not valid")
274         end
275       end
276 
277       def check_nonce
278         case openid_namespace
279         when OPENID1_NS
280           nonce =
281             @message.get_arg(BARE_NS, Consumer.openid1_return_to_nonce_name)
282 
283           # We generated the nonce, so it uses the empty string as the
284           # server URL
285           server_url = ''
286         when OPENID2_NS
287           nonce = @message.get_arg(OPENID2_NS, 'response_nonce')
288           server_url = self.server_url
289         else
290           raise StandardError, 'Not reached'
291         end
292 
293         if nonce.nil?
294           raise ProtocolError, 'Nonce missing from response'
295         end
296 
297         begin
298           time, extra = Nonce.split_nonce(nonce)
299         rescue ArgumentError => why
300           raise ProtocolError, "Malformed nonce: #{nonce.inspect}"
301         end
302 
303         if !@store.nil? && !@store.use_nonce(server_url, time, extra)
304           raise ProtocolError, ("Nonce already used or out of range: "\
305                                "#{nonce.inspect}")
306         end
307       end
308 
309       def verify_discovery_results
310         begin
311           case openid_namespace
312           when OPENID1_NS
313             verify_discovery_results_openid1
314           when OPENID2_NS
315             verify_discovery_results_openid2
316           else
317             raise StandardError, "Not reached: #{openid_namespace}"
318           end
319         rescue Message::KeyNotFound => why
320           raise ProtocolError, "Missing required field: #{why.message}"
321         end
322       end
323 
324       def verify_discovery_results_openid2
325         to_match = OpenIDServiceEndpoint.new
326         to_match.type_uris = [OPENID_2_0_TYPE]
327         to_match.claimed_id = fetch('claimed_id', nil)
328         to_match.local_id = fetch('identity', nil)
329         to_match.server_url = fetch('op_endpoint')
330 
331         if to_match.claimed_id.nil? && !to_match.local_id.nil?
332           raise ProtocolError, ('openid.identity is present without '\
333                                 'openid.claimed_id')
334         elsif !to_match.claimed_id.nil? && to_match.local_id.nil?
335           raise ProtocolError, ('openid.claimed_id is present without '\
336                                 'openid.identity')
337 
338         # This is a response without identifiers, so there's really no
339         # checking that we can do, so return an endpoint that's for
340         # the specified `openid.op_endpoint'
341         elsif to_match.claimed_id.nil?
342           @endpoint =
343             OpenIDServiceEndpoint.from_op_endpoint_url(to_match.server_url)
344           return
345         end
346 
347         if @endpoint.nil?
348           Util.log('No pre-discovered information supplied')
349           discover_and_verify(to_match.claimed_id, [to_match])
350         else
351           begin
352             verify_discovery_single(@endpoint, to_match)
353           rescue ProtocolError => why
354             Util.log("Error attempting to use stored discovery "\
355                      "information: #{why.message}")
356             Util.log("Attempting discovery to verify endpoint")
357             discover_and_verify(to_match.claimed_id, [to_match])
358           end
359         end
360 
361         if @endpoint.claimed_id != to_match.claimed_id
362           @endpoint = @endpoint.dup
363           @endpoint.claimed_id = to_match.claimed_id
364         end
365       end
366 
367       def verify_discovery_results_openid1
368         claimed_id =
369           @message.get_arg(BARE_NS, Consumer.openid1_return_to_claimed_id_name)
370 
371         if claimed_id.nil?
372           if @endpoint.nil?
373             raise ProtocolError, ("When using OpenID 1, the claimed ID must "\
374                                   "be supplied, either by passing it through "\
375                                   "as a return_to parameter or by using a "\
376                                   "session, and supplied to the IdResHandler "\
377                                   "when it is constructed.")
378           else
379             claimed_id = @endpoint.claimed_id
380           end
381         end
382 
383         to_match = OpenIDServiceEndpoint.new
384         to_match.type_uris = [OPENID_1_1_TYPE]
385         to_match.local_id = fetch('identity')
386         # Restore delegate information from the initiation phase
387         to_match.claimed_id = claimed_id
388 
389         to_match_1_0 = to_match.dup
390         to_match_1_0.type_uris = [OPENID_1_0_TYPE]
391 
392         if !@endpoint.nil?
393           begin
394             begin
395               verify_discovery_single(@endpoint, to_match)
396             rescue TypeURIMismatch
397               verify_discovery_single(@endpoint, to_match_1_0)
398             end
399           rescue ProtocolError => why
400             Util.log('Error attempting to use stored discovery information: ' +
401                      why.message)
402             Util.log('Attempting discovery to verify endpoint')
403           else
404             return @endpoint
405           end
406         end
407 
408         # Either no endpoint was supplied or OpenID 1.x verification
409         # of the information that's in the message failed on that
410         # endpoint.
411         discover_and_verify(to_match.claimed_id, [to_match, to_match_1_0])
412       end
413 
414       # Given an endpoint object created from the information in an
415       # OpenID response, perform discovery and verify the discovery
416       # results, returning the matching endpoint that is the result of
417       # doing that discovery.
418       def discover_and_verify(claimed_id, to_match_endpoints)
419         Util.log("Performing discovery on #{claimed_id}")
420         _, services = OpenID.discover(claimed_id)
421         if services.length == 0
422           # XXX: this might want to be something other than
423           # ProtocolError. In Python, it's DiscoveryFailure
424           raise ProtocolError, ("No OpenID information found at "\
425                                 "#{claimed_id}")
426         end
427         verify_discovered_services(claimed_id, services, to_match_endpoints)
428       end
429 
430 
431       def verify_discovered_services(claimed_id, services, to_match_endpoints)
432         # Search the services resulting from discovery to find one
433         # that matches the information from the assertion
434         failure_messages = []
435         for endpoint in services
436           for to_match_endpoint in to_match_endpoints
437             begin
438               verify_discovery_single(endpoint, to_match_endpoint)
439             rescue ProtocolError => why
440               failure_messages << why.message
441             else
442               # It matches, so discover verification has
443               # succeeded. Return this endpoint.
444               @endpoint = endpoint
445               return
446             end
447           end
448         end
449 
450         Util.log("Discovery verification failure for #{claimed_id}")
451         failure_messages.each do |failure_message|
452           Util.log(" * Endpoint mismatch: " + failure_message)
453         end
454 
455         # XXX: is DiscoveryFailure in Python OpenID
456         raise ProtocolError, ("No matching endpoint found after "\
457                               "discovering #{claimed_id}")
458       end
459 
460       def verify_discovery_single(endpoint, to_match)
461         # Every type URI that's in the to_match endpoint has to be
462         # present in the discovered endpoint.
463         for type_uri in to_match.type_uris
464           if !endpoint.uses_extension(type_uri)
465             raise TypeURIMismatch.new(type_uri, endpoint)
466           end
467         end
468 
469         # Fragments do not influence discovery, so we can't compare a
470         # claimed identifier with a fragment to discovered information.
471         defragged_claimed_id =
472           case Yadis::XRI.identifier_scheme(endpoint.claimed_id)
473           when :xri
474             endpoint.claimed_id
475           when :uri
476             begin
477               parsed = URI.parse(endpoint.claimed_id)
478             rescue URI::InvalidURIError
479               endpoint.claimed_id
480             else
481               parsed.fragment = nil
482               parsed.to_s
483             end
484           else
485             raise StandardError, 'Not reached'
486           end
487 
488         if defragged_claimed_id != endpoint.claimed_id
489           raise ProtocolError, ("Claimed ID does not match (different "\
490                                 "subjects!), Expected "\
491                                 "#{defragged_claimed_id}, got "\
492                                 "#{endpoint.claimed_id}")
493         end
494 
495         if to_match.get_local_id != endpoint.get_local_id
496           raise ProtocolError, ("local_id mismatch. Expected "\
497                                 "#{to_match.get_local_id}, got "\
498                                 "#{endpoint.get_local_id}")
499         end
500 
501         # If the server URL is nil, this must be an OpenID 1
502         # response, because op_endpoint is a required parameter in
503         # OpenID 2. In that case, we don't actually care what the
504         # discovered server_url is, because signature checking or
505         # check_auth should take care of that check for us.
506         if to_match.server_url.nil?
507           if to_match.preferred_namespace != OPENID1_NS
508             raise StandardError,
509             "The code calling this must ensure that OpenID 2 "\
510             "responses have a non-none `openid.op_endpoint' and "\
511             "that it is set as the `server_url' attribute of the "\
512             "`to_match' endpoint."
513           end
514         elsif to_match.server_url != endpoint.server_url
515           raise ProtocolError, ("OP Endpoint mismatch. Expected"\
516                                 "#{to_match.server_url}, got "\
517                                 "#{endpoint.server_url}")
518         end
519       end
520 
521     end
522   end
523 end

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

Valid XHTML 1.0! Valid CSS!