C0 code coverage information

Generated on Fri Jul 11 15:55:31 -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/extensions/ax.rb 516 360
95.2% 
93.3% 
  1 # Implements the OpenID attribute exchange specification, version 1.0
  2 
  3 require 'openid/extension'
  4 require 'openid/trustroot'
  5 require 'openid/message'
  6 
  7 module OpenID
  8   module AX
  9 
 10     UNLIMITED_VALUES = "unlimited"
 11     MINIMUM_SUPPORTED_ALIAS_LENGTH = 32
 12 
 13     # check alias for invalid characters, raise AXError if found
 14     def self.check_alias(name)
 15       if name.match(/(,|\.)/)
 16         raise Error, ("Alias #{name.inspect} must not contain a "\
 17                       "comma or period.")
 18       end
 19     end
 20 
 21     # Raised when data does not comply with AX 1.0 specification
 22     class Error < ArgumentError
 23     end
 24 
 25     # Abstract class containing common code for attribute exchange messages
 26     class AXMessage < Extension
 27       attr_accessor :ns_alias, :mode, :ns_uri
 28 
 29       NS_URI = 'http://openid.net/srv/ax/1.0'
 30       def initialize
 31         @ns_alias = 'ax'
 32         @ns_uri = NS_URI
 33         @mode = nil
 34       end
 35 
 36       protected
 37 
 38       # Raise an exception if the mode in the attribute exchange
 39       # arguments does not match what is expected for this class.
 40       def check_mode(ax_args)
 41         actual_mode = ax_args['mode']
 42         if actual_mode != @mode
 43           raise Error, "Expected mode #{mode.inspect}, got #{actual_mode.inspect}"
 44         end
 45       end
 46 
 47       def new_args
 48         {'mode' => @mode}
 49       end
 50     end
 51 
 52     # Represents a single attribute in an attribute exchange
 53     # request. This should be added to an Request object in order to
 54     # request the attribute.
 55     #
 56     # @ivar required: Whether the attribute will be marked as required
 57     #     when presented to the subject of the attribute exchange
 58     #     request.
 59     # @type required: bool
 60     #
 61     # @ivar count: How many values of this type to request from the
 62     #      subject. Defaults to one.
 63     # @type count: int
 64     #
 65     # @ivar type_uri: The identifier that determines what the attribute
 66     #      represents and how it is serialized. For example, one type URI
 67     #      representing dates could represent a Unix timestamp in base 10
 68     #      and another could represent a human-readable string.
 69     # @type type_uri: str
 70     #
 71     # @ivar ns_alias: The name that should be given to this alias in the
 72     #      request. If it is not supplied, a generic name will be
 73     #      assigned. For example, if you want to call a Unix timestamp
 74     #      value 'tstamp', set its alias to that value. If two attributes
 75     #      in the same message request to use the same alias, the request
 76     #      will fail to be generated.
 77     # @type alias: str or NoneType
 78     class AttrInfo < Object
 79       attr_reader :type_uri, :count, :ns_alias
 80       attr_accessor :required
 81       def initialize(type_uri, ns_alias=nil, required=false, count=1)
 82         @type_uri = type_uri
 83         @count = count
 84         @required = required
 85         @ns_alias = ns_alias
 86       end
 87 
 88       def wants_unlimited_values?
 89         @count == UNLIMITED_VALUES
 90       end
 91     end
 92 
 93     # Given a namespace mapping and a string containing a
 94     # comma-separated list of namespace aliases, return a list of type
 95     # URIs that correspond to those aliases.
 96     # namespace_map: OpenID::NamespaceMap
 97     def self.to_type_uris(namespace_map, alias_list_s)
 98       return [] if alias_list_s.nil?
 99       alias_list_s.split(',').inject([]) {|uris, name|
100         type_uri = namespace_map.get_namespace_uri(name)
101         raise IndexError, "No type defined for attribute name #{name.inspect}" if type_uri.nil?
102         uris << type_uri
103       }
104     end
105 
106 
107     # An attribute exchange 'fetch_request' message. This message is
108     # sent by a relying party when it wishes to obtain attributes about
109     # the subject of an OpenID authentication request.
110     class FetchRequest < AXMessage
111       attr_reader :requested_attributes
112       attr_accessor :update_url
113 
114       def initialize(update_url = nil)
115         super()
116         @mode = 'fetch_request'
117         @requested_attributes = {}
118         @update_url = update_url
119       end
120 
121       # Add an attribute to this attribute exchange request.
122       # attribute: AttrInfo, the attribute being requested
123       # Raises IndexError if the requested attribute is already present
124       #   in this request.
125       def add(attribute)
126         if @requested_attributes[attribute.type_uri]
127           raise IndexError, "The attribute #{attribute.type_uri} has already been requested"
128         end
129         @requested_attributes[attribute.type_uri] = attribute
130       end
131 
132       # Get the serialized form of this attribute fetch request.
133       # returns a hash of the arguments
134       def get_extension_args
135         aliases = NamespaceMap.new
136         required = []
137         if_available = []
138         ax_args = new_args 
139         @requested_attributes.each{|type_uri, attribute|
140           if attribute.ns_alias
141             name = aliases.add_alias(type_uri, attribute.ns_alias)
142           else
143             name = aliases.add(type_uri)
144           end
145           if attribute.required
146             required << name
147           else
148             if_available << name
149           end
150           if attribute.count != 1
151             ax_args["count.#{name}"] = attribute.count.to_s
152           end
153           ax_args["type.#{name}"] = type_uri
154         }
155 
156         unless required.empty?
157           ax_args['required'] = required.join(',')
158         end
159         unless if_available.empty?
160           ax_args['if_available'] = if_available.join(',')
161         end
162         return ax_args
163       end
164 
165       # Get the type URIs for all attributes that have been marked
166       # as required.
167       def get_required_attrs
168         @requested_attributes.inject([]) {|required, (type_uri, attribute)|
169           if attribute.required
170             required << type_uri
171           else
172             required
173           end
174         }
175       end
176 
177       # Extract a FetchRequest from an OpenID message
178       # message: OpenID::Message
179       # return a FetchRequest or nil if AX arguments are not present
180       def self.from_openid_request(oidreq)
181         message = oidreq.message
182         ax_args = message.get_args(NS_URI)
183         return nil if ax_args == {}
184         req = new
185         req.parse_extension_args(ax_args)
186 
187         if req.update_url
188           realm = message.get_arg(OPENID_NS, 'realm',
189                                   message.get_arg(OPENID_NS, 'return_to'))
190           if realm.nil? or realm.empty?
191             raise Error, "Cannot validate update_url #{req.update_url.inspect} against absent realm"
192           end
193           tr = TrustRoot::TrustRoot.parse(realm)
194           unless tr.validate_url(req.update_url)
195             raise Error, "Update URL #{req.update_url.inspect} failed validation against realm #{realm.inspect}"
196           end
197         end
198 
199         return req
200       end
201 
202       def parse_extension_args(ax_args)
203         check_mode(ax_args)
204 
205         aliases = NamespaceMap.new
206 
207         ax_args.each{|k,v|
208           if k.index('type.') == 0
209             name = k[5..-1]
210             type_uri = v
211             aliases.add_alias(type_uri, name)
212 
213             count_key = 'count.'+name
214             count_s = ax_args[count_key]
215             count = 1
216             if count_s
217               if count_s == UNLIMITED_VALUES
218                 count = count_s
219               else
220                 count = count_s.to_i
221                 if count <= 0
222                   raise Error, "Invalid value for count #{count_key.inspect}: #{count_s.inspect}"
223                 end
224               end
225             end
226             add(AttrInfo.new(type_uri, name, false, count))
227           end
228         }
229 
230         required = AX.to_type_uris(aliases, ax_args['required'])
231         required.each{|type_uri|
232           @requested_attributes[type_uri].required = true
233         }
234         if_available = AX.to_type_uris(aliases, ax_args['if_available'])
235         all_type_uris = required + if_available
236 
237         aliases.namespace_uris.each{|type_uri|
238           unless all_type_uris.member? type_uri
239             raise Error, "Type URI #{type_uri.inspect} was in the request but not present in 'required' or 'if_available'"
240           end
241         }
242         @update_url = ax_args['update_url']
243       end
244 
245       # return the list of AttrInfo objects contained in the FetchRequest
246       def attributes
247         @requested_attributes.values
248       end
249 
250       # return the list of requested attribute type URIs
251       def requested_types
252         @requested_attributes.keys
253       end
254 
255       def member?(type_uri)
256         ! @requested_attributes[type_uri].nil?
257       end
258 
259     end
260 
261     # Abstract class that implements a message that has attribute
262     # keys and values. It contains the common code between
263     # fetch_response and store_request.
264     class KeyValueMessage < AXMessage
265       attr_reader :data
266       def initialize
267         super()
268         @mode = nil
269         @data = {}
270         @data.default = []
271       end
272 
273       # Add a single value for the given attribute type to the
274       # message. If there are already values specified for this type,
275       # this value will be sent in addition to the values already
276       # specified.
277       def add_value(type_uri, value)
278         @data[type_uri] = @data[type_uri] << value
279       end
280 
281       # Set the values for the given attribute type. This replaces
282       # any values that have already been set for this attribute.
283       def set_values(type_uri, values)
284         @data[type_uri] = values
285       end
286 
287       # Get the extension arguments for the key/value pairs
288       # contained in this message.
289       def _get_extension_kv_args(aliases = nil)
290         aliases = NamespaceMap.new if aliases.nil?
291 
292         ax_args = new_args
293 
294         @data.each{|type_uri, values|
295           name = aliases.add(type_uri)
296           ax_args['type.'+name] = type_uri
297           ax_args['count.'+name] = values.size.to_s
298 
299           values.each_with_index{|value, i|
300             key = "value.#{name}.#{i+1}"
301             ax_args[key] = value
302           }
303         }
304         return ax_args
305       end
306 
307       # Parse attribute exchange key/value arguments into this object.
308 
309       def parse_extension_args(ax_args)
310         check_mode(ax_args)
311         aliases = NamespaceMap.new
312 
313         ax_args.each{|k, v|
314           if k.index('type.') == 0
315             type_uri = v
316             name = k[5..-1]
317 
318             AX.check_alias(name)
319             aliases.add_alias(type_uri,name)
320           end
321         }
322 
323         aliases.each{|type_uri, name|
324           count_s = ax_args['count.'+name]
325           count = count_s.to_i
326           if count_s.nil?
327             value = ax_args['value.'+name]
328             if value.nil?
329               raise IndexError, "Missing #{'value.'+name} in FetchResponse" 
330             elsif value.empty?
331               values = []
332             else
333               values = [value]
334             end
335           elsif count_s.to_i == 0
336             values = []
337           else
338             values = (1..count).inject([]){|l,i|
339               key = "value.#{name}.#{i}"
340               v = ax_args[key]
341               raise IndexError, "Missing #{key} in FetchResponse" if v.nil?
342               l << v
343             }
344           end
345           @data[type_uri] = values
346         }
347       end
348 
349       # Get a single value for an attribute. If no value was sent
350       # for this attribute, use the supplied default. If there is more
351       # than one value for this attribute, this method will fail.
352       def get_single(type_uri, default = nil)
353         values = @data[type_uri]
354         return default if values.empty?
355         if values.size != 1
356           raise Error, "More than one value present for #{type_uri.inspect}"
357         else
358           return values[0]
359         end
360       end
361 
362       # retrieve the list of values for this attribute
363       def get(type_uri)
364         @data[type_uri]
365       end
366       
367       # retrieve the list of values for this attribute
368       def [](type_uri)
369         @data[type_uri]
370       end
371 
372       # get the number of responses for this attribute
373       def count(type_uri)
374         @data[type_uri].size
375       end
376 
377     end
378 
379     # A fetch_response attribute exchange message
380     class FetchResponse < KeyValueMessage
381       attr_reader :update_url
382 
383       def initialize(update_url = nil)
384         super()
385         @mode = 'fetch_response'
386         @update_url = update_url
387       end
388 
389       # Serialize this object into arguments in the attribute
390       # exchange namespace
391       # Takes an optional FetchRequest.  If specified, the response will be
392       # validated against this request, and empty responses for requested
393       # fields with no data will be sent.
394       def get_extension_args(request = nil)
395         aliases = NamespaceMap.new
396         zero_value_types = []
397 
398         if request
399           # Validate the data in the context of the request (the
400           # same attributes should be present in each, and the
401           # counts in the response must be no more than the counts
402           # in the request)
403           @data.keys.each{|type_uri|
404             unless request.member? type_uri
405               raise IndexError, "Response attribute not present in request: #{type_uri.inspect}"
406             end
407           }
408 
409           request.attributes.each{|attr_info|
410             # Copy the aliases from the request so that reading
411             # the response in light of the request is easier
412             if attr_info.ns_alias.nil?
413               aliases.add(attr_info.type_uri)
414             else
415               aliases.add_alias(attr_info.type_uri, attr_info.ns_alias)
416             end
417             values = @data[attr_info.type_uri]
418             if values.empty? # @data defaults to []
419               zero_value_types << attr_info
420             end
421             if attr_info.count != UNLIMITED_VALUES and attr_info.count < values.size
422               raise Error, "More than the number of requested values were specified for #{attr_info.type_uri.inspect}"
423             end
424           }
425         end
426 
427         kv_args = _get_extension_kv_args(aliases)
428 
429         # Add the KV args into the response with the args that are
430         # unique to the fetch_response
431         ax_args = new_args
432 
433         zero_value_types.each{|attr_info|
434           name = aliases.get_alias(attr_info.type_uri)
435           kv_args['type.' + name] = attr_info.type_uri
436           kv_args['count.' + name] = '0'
437         }
438         update_url = (request and request.update_url or @update_url)
439         ax_args['update_url'] = update_url unless update_url.nil?
440         ax_args.update(kv_args)
441         return ax_args
442       end
443 
444       def parse_extension_args(ax_args)
445         super
446         @update_url = ax_args['update_url']
447       end
448 
449       # Construct a FetchResponse object from an OpenID library
450       # SuccessResponse object.
451       def self.from_success_response(success_response, signed=true)
452         obj = self.new
453         if signed
454           ax_args = success_response.get_signed_ns(obj.ns_uri)
455         else
456           ax_args = success_response.message.get_args(obj.ns_uri)
457         end
458 
459         begin
460           obj.parse_extension_args(ax_args)
461           return obj
462         rescue Error => e
463           return nil
464         end
465       end
466     end
467 
468     # A store request attribute exchange message representation
469     class StoreRequest < KeyValueMessage
470       def initialize
471         super
472         @mode = 'store_request'
473       end
474 
475       def get_extension_args(aliases=nil)
476         ax_args = new_args
477         kv_args = _get_extension_kv_args(aliases)
478         ax_args.update(kv_args)
479         return ax_args
480       end
481     end
482 
483     # An indication that the store request was processed along with
484     # this OpenID transaction.
485     class StoreResponse < AXMessage
486       SUCCESS_MODE = 'store_response_success'
487       FAILURE_MODE = 'store_response_failure'
488       attr_reader :error_message
489 
490       def initialize(succeeded = true, error_message = nil)
491         super()
492         if succeeded and error_message
493           raise Error, "Error message included in a success response"
494         end
495         if succeeded
496           @mode = SUCCESS_MODE
497         else
498           @mode = FAILURE_MODE
499         end
500         @error_message = error_message
501       end
502 
503       def succeeded?
504         @mode == SUCCESS_MODE
505       end
506 
507       def get_extension_args
508         ax_args = new_args
509         if !succeeded? and error_message
510           ax_args['error'] = @error_message
511         end
512         return ax_args
513       end
514     end
515   end
516 end

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

Valid XHTML 1.0! Valid CSS!