C0 code coverage information
Generated on Fri Jul 11 15:55:34 -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.
1 require 'openid/util'
2 require 'openid/kvform'
3
4 module OpenID
5
6 IDENTIFIER_SELECT = 'http://specs.openid.net/auth/2.0/identifier_select'
7
8 # URI for Simple Registration extension, the only commonly deployed
9 # OpenID 1.x extension, and so a special case.
10 SREG_URI = 'http://openid.net/sreg/1.0'
11
12 # The OpenID 1.x namespace URIs
13 OPENID1_NS = 'http://openid.net/signon/1.0'
14 OPENID11_NS = 'http://openid.net/signon/1.1'
15 OPENID1_NAMESPACES = [OPENID1_NS, OPENID11_NS]
16
17 # The OpenID 2.0 namespace URI
18 OPENID2_NS = 'http://specs.openid.net/auth/2.0'
19
20 # The namespace consisting of pairs with keys that are prefixed with
21 # "openid." but not in another namespace.
22 NULL_NAMESPACE = :null_namespace
23
24 # The null namespace, when it is an allowed OpenID namespace
25 OPENID_NS = :openid_namespace
26
27 # The top-level namespace, excluding all pairs with keys that start
28 # with "openid."
29 BARE_NS = :bare_namespace
30
31 # Limit, in bytes, of identity provider and return_to URLs,
32 # including response payload. See OpenID 1.1 specification,
33 # Appendix D.
34 OPENID1_URL_LIMIT = 2047
35
36 # All OpenID protocol fields. Used to check namespace aliases.
37 OPENID_PROTOCOL_FIELDS = [
38 'ns', 'mode', 'error', 'return_to',
39 'contact', 'reference', 'signed',
40 'assoc_type', 'session_type',
41 'dh_modulus', 'dh_gen',
42 'dh_consumer_public', 'claimed_id',
43 'identity', 'realm', 'invalidate_handle',
44 'op_endpoint', 'response_nonce', 'sig',
45 'assoc_handle', 'trust_root', 'openid',
46 ]
47
48 # Sentinel used for Message implementation to indicate that getArg
49 # should raise an exception instead of returning a default.
50 NO_DEFAULT = :no_default
51
52 # Raised if the generic OpenID namespace is accessed when there
53 # is no OpenID namespace set for this message.
54 class UndefinedOpenIDNamespace < Exception; end
55
56 # Raised when an alias or namespace URI has already been registered.
57 class NamespaceAliasRegistrationError < Exception; end
58
59 # Raised if openid.ns is not a recognized value.
60 # See Message class variable @@allowed_openid_namespaces
61 class InvalidOpenIDNamespace < Exception; end
62
63 class Message
64 attr_reader :namespaces
65
66 # Raised when key lookup fails
67 class KeyNotFound < IndexError ; end
68
69 # Namespace / alias registration map. See
70 # register_namespace_alias.
71 @@registered_aliases = {}
72
73 # Registers a (namespace URI, alias) mapping in a global namespace
74 # alias map. Raises NamespaceAliasRegistrationError if either the
75 # namespace URI or alias has already been registered with a
76 # different value. This function is required if you want to use a
77 # namespace with an OpenID 1 message.
78 def Message.register_namespace_alias(namespace_uri, alias_)
79 if @@registered_aliases[alias_] == namespace_uri
80 return
81 end
82
83 if @@registered_aliases.values.include?(namespace_uri)
84 raise NamespaceAliasRegistrationError,
85 'Namespace uri #{namespace_uri} already registered'
86 end
87
88 if @@registered_aliases.member?(alias_)
89 raise NamespaceAliasRegistrationError,
90 'Alias #{alias_} already registered'
91 end
92
93 @@registered_aliases[alias_] = namespace_uri
94 end
95
96 @@allowed_openid_namespaces = [OPENID1_NS, OPENID2_NS, OPENID11_NS]
97
98 # Raises InvalidNamespaceError if you try to instantiate a Message
99 # with a namespace not in the above allowed list
100 def initialize(openid_namespace=nil)
101 @args = {}
102 @namespaces = NamespaceMap.new
103 if openid_namespace
104 implicit = OPENID1_NAMESPACES.member? openid_namespace
105 self.set_openid_namespace(openid_namespace, implicit)
106 else
107 @openid_ns_uri = nil
108 end
109 end
110
111 # Construct a Message containing a set of POST arguments.
112 # Raises InvalidNamespaceError if you try to instantiate a Message
113 # with a namespace not in the above allowed list
114 def Message.from_post_args(args)
115 m = Message.new
116 openid_args = {}
117 args.each do |key,value|
118 if value.is_a?(Array)
119 raise ArgumentError, "Query dict must have one value for each key, " +
120 "not lists of values. Query is #{args.inspect}"
121 end
122
123 prefix, rest = key.split('.', 2)
124
125 if prefix != 'openid' or rest.nil?
126 m.set_arg(BARE_NS, key, value)
127 else
128 openid_args[rest] = value
129 end
130 end
131
132 m._from_openid_args(openid_args)
133 return m
134 end
135
136 # Construct a Message from a parsed KVForm message.
137 # Raises InvalidNamespaceError if you try to instantiate a Message
138 # with a namespace not in the above allowed list
139 def Message.from_openid_args(openid_args)
140 m = Message.new
141 m._from_openid_args(openid_args)
142 return m
143 end
144
145 # Raises InvalidNamespaceError if you try to instantiate a Message
146 # with a namespace not in the above allowed list
147 def _from_openid_args(openid_args)
148 ns_args = []
149
150 # resolve namespaces
151 openid_args.each { |rest, value|
152 ns_alias, ns_key = rest.split('.', 2)
153 if ns_key.nil?
154 ns_alias = NULL_NAMESPACE
155 ns_key = rest
156 end
157
158 if ns_alias == 'ns'
159 @namespaces.add_alias(value, ns_key)
160 elsif ns_alias == NULL_NAMESPACE and ns_key == 'ns'
161 set_openid_namespace(value, false)
162 else
163 ns_args << [ns_alias, ns_key, value]
164 end
165 }
166
167 # implicitly set an OpenID 1 namespace
168 unless get_openid_namespace
169 set_openid_namespace(OPENID1_NS, true)
170 end
171
172 # put the pairs into the appropriate namespaces
173 ns_args.each { |ns_alias, ns_key, value|
174 ns_uri = @namespaces.get_namespace_uri(ns_alias)
175 unless ns_uri
176 ns_uri = _get_default_namespace(ns_alias)
177 unless ns_uri
178 ns_uri = get_openid_namespace
179 ns_key = "#{ns_alias}.#{ns_key}"
180 else
181 @namespaces.add_alias(ns_uri, ns_alias, true)
182 end
183 end
184 self.set_arg(ns_uri, ns_key, value)
185 }
186 end
187
188 def _get_default_namespace(mystery_alias)
189 # only try to map an alias to a default if it's an
190 # OpenID 1.x namespace
191 if is_openid1
192 @@registered_aliases[mystery_alias]
193 end
194 end
195
196 def set_openid_namespace(openid_ns_uri, implicit)
197 if !@@allowed_openid_namespaces.include?(openid_ns_uri)
198 raise InvalidOpenIDNamespace, "Invalid null namespace: #{openid_ns_uri}"
199 end
200 @namespaces.add_alias(openid_ns_uri, NULL_NAMESPACE, implicit)
201 @openid_ns_uri = openid_ns_uri
202 end
203
204 def get_openid_namespace
205 return @openid_ns_uri
206 end
207
208 def is_openid1
209 return OPENID1_NAMESPACES.member?(@openid_ns_uri)
210 end
211
212 def is_openid2
213 return @openid_ns_uri == OPENID2_NS
214 end
215
216 # Create a message from a KVForm string
217 def Message.from_kvform(kvform_string)
218 return Message.from_openid_args(Util.kv_to_dict(kvform_string))
219 end
220
221 def copy
222 return Marshal.load(Marshal.dump(self))
223 end
224
225 # Return all arguments with "openid." in from of namespaced arguments.
226 def to_post_args
227 args = {}
228
229 # add namespace defs to the output
230 @namespaces.each { |ns_uri, ns_alias|
231 if @namespaces.implicit?(ns_uri)
232 next
233 end
234 if ns_alias == NULL_NAMESPACE
235 ns_key = 'openid.ns'
236 else
237 ns_key = 'openid.ns.' + ns_alias
238 end
239 args[ns_key] = ns_uri
240 }
241
242 @args.each { |k, value|
243 ns_uri, ns_key = k
244 key = get_key(ns_uri, ns_key)
245 args[key] = value
246 }
247
248 return args
249 end
250
251 # Return all namespaced arguments, failing if any non-namespaced arguments
252 # exist.
253 def to_args
254 post_args = self.to_post_args
255 kvargs = {}
256 post_args.each { |k,v|
257 if !k.starts_with?('openid.')
258 raise ArgumentError, "This message can only be encoded as a POST, because it contains arguments that are not prefixed with 'openid.'"
259 else
260 kvargs[k[7..-1]] = v
261 end
262 }
263 return kvargs
264 end
265
266 # Generate HTML form markup that contains the values in this
267 # message, to be HTTP POSTed as x-www-form-urlencoded UTF-8.
268 def to_form_markup(action_url, form_tag_attrs=nil, submit_text='Continue')
269 form_tag_attr_map = {}
270
271 if form_tag_attrs
272 form_tag_attrs.each { |name, attr|
273 form_tag_attr_map[name] = attr
274 }
275 end
276
277 form_tag_attr_map['action'] = action_url
278 form_tag_attr_map['method'] = 'post'
279 form_tag_attr_map['accept-charset'] = 'UTF-8'
280 form_tag_attr_map['enctype'] = 'application/x-www-form-urlencoded'
281
282 markup = "<form "
283
284 form_tag_attr_map.each { |k, v|
285 markup += " #{k}=\"#{v}\""
286 }
287
288 markup += ">\n"
289
290 to_post_args.each { |k,v|
291 markup += "<input type='hidden' name='#{k}' value='#{v}' />\n"
292 }
293 markup += "<input type='submit' value='#{submit_text}' />\n"
294 markup += "\n</form>"
295 return markup
296 end
297
298 # Generate a GET URL with the paramters in this message attacked as
299 # query parameters.
300 def to_url(base_url)
301 return Util.append_args(base_url, self.to_post_args)
302 end
303
304 # Generate a KVForm string that contains the parameters in this message.
305 # This will fail is the message contains arguments outside of the
306 # "openid." prefix.
307 def to_kvform
308 return Util.dict_to_kv(to_args)
309 end
310
311 # Generate an x-www-urlencoded string.
312 def to_url_encoded
313 args = to_post_args.map.sort
314 return Util.urlencode(args)
315 end
316
317 # Convert an input value into the internally used values of this obejct.
318 def _fix_ns(namespace)
319 if namespace == OPENID_NS
320 unless @openid_ns_uri
321 raise UndefinedOpenIDNamespace, 'OpenID namespace not set'
322 else
323 namespace = @openid_ns_uri
324 end
325 end
326
327 if namespace == BARE_NS
328 return namespace
329 end
330
331 if !namespace.is_a?(String)
332 raise ArgumentError, ("Namespace must be BARE_NS, OPENID_NS or "\
333 "a string. Got #{namespace.inspect}")
334 end
335
336 if namespace.index(':').nil?
337 msg = ("OpenID 2.0 namespace identifiers SHOULD be URIs. "\
338 "Got #{namespace.inspect}")
339 Util.log(msg)
340
341 if namespace == 'sreg'
342 msg = "Using #{SREG_URI} instead of \"sreg\" as namespace"
343 Util.log(msg)
344 return SREG_URI
345 end
346 end
347
348 return namespace
349 end
350
351 def has_key?(namespace, ns_key)
352 namespace = _fix_ns(namespace)
353 return @args.member?([namespace, ns_key])
354 end
355
356 # Get the key for a particular namespaced argument
357 def get_key(namespace, ns_key)
358 namespace = _fix_ns(namespace)
359 return ns_key if namespace == BARE_NS
360
361 ns_alias = @namespaces.get_alias(namespace)
362
363 # no alias is defined, so no key can exist
364 return nil if ns_alias.nil?
365
366 if ns_alias == NULL_NAMESPACE
367 tail = ns_key
368 else
369 tail = "#{ns_alias}.#{ns_key}"
370 end
371
372 return 'openid.' + tail
373 end
374
375 # Get a value for a namespaced key.
376 def get_arg(namespace, key, default=nil)
377 namespace = _fix_ns(namespace)
378 @args.fetch([namespace, key]) {
379 if default == NO_DEFAULT
380 raise KeyNotFound, "<#{namespace}>#{key} not in this message"
381 else
382 default
383 end
384 }
385 end
386
387 # Get the arguments that are defined for this namespace URI.
388 def get_args(namespace)
389 namespace = _fix_ns(namespace)
390 args = {}
391 @args.each { |k,v|
392 pair_ns, ns_key = k
393 args[ns_key] = v if pair_ns == namespace
394 }
395 return args
396 end
397
398 # Set multiple key/value pairs in one call.
399 def update_args(namespace, updates)
400 namespace = _fix_ns(namespace)
401 updates.each {|k,v| set_arg(namespace, k, v)}
402 end
403
404 # Set a single argument in this namespace
405 def set_arg(namespace, key, value)
406 namespace = _fix_ns(namespace)
407 @args[[namespace, key].freeze] = value
408 if namespace != BARE_NS
409 @namespaces.add(namespace)
410 end
411 end
412
413 # Remove a single argument from this namespace.
414 def del_arg(namespace, key)
415 namespace = _fix_ns(namespace)
416 _key = [namespace, key]
417 @args.delete(_key)
418 end
419
420 def ==(other)
421 other.is_a?(self.class) && @args == other.instance_eval { @args }
422 end
423
424 def get_aliased_arg(aliased_key, default=nil)
425 if aliased_key == 'ns'
426 return get_openid_namespace()
427 end
428
429 ns_alias, key = aliased_key.split('.', 2)
430 if ns_alias == 'ns'
431 uri = @namespaces.get_namespace_uri(key)
432 if uri.nil? and default == NO_DEFAULT
433 raise KeyNotFound, "Namespace #{key} not defined when looking "\
434 "for #{aliased_key}"
435 else
436 return (uri.nil? ? default : uri)
437 end
438 end
439
440 if key.nil?
441 key = aliased_key
442 ns = nil
443 else
444 ns = @namespaces.get_namespace_uri(ns_alias)
445 end
446
447 if ns.nil?
448 key = aliased_key
449 ns = get_openid_namespace
450 end
451
452 return get_arg(ns, key, default)
453 end
454 end
455
456
457 # Maintains a bidirectional map between namespace URIs and aliases.
458 class NamespaceMap
459
460 def initialize
461 @alias_to_namespace = {}
462 @namespace_to_alias = {}
463 @implicit_namespaces = []
464 end
465
466 def get_alias(namespace_uri)
467 @namespace_to_alias[namespace_uri]
468 end
469
470 def get_namespace_uri(namespace_alias)
471 @alias_to_namespace[namespace_alias]
472 end
473
474 # Add an alias from this namespace URI to the alias.
475 def add_alias(namespace_uri, desired_alias, implicit=false)
476 # Check that desired_alias is not an openid protocol field as
477 # per the spec.
478 Util.assert(!OPENID_PROTOCOL_FIELDS.include?(desired_alias),
479 "#{desired_alias} is not an allowed namespace alias")
480
481 # check that there is not a namespace already defined for the
482 # desired alias
483 current_namespace_uri = @alias_to_namespace.fetch(desired_alias, nil)
484 if current_namespace_uri and current_namespace_uri != namespace_uri
485 raise IndexError, "Cannot map #{namespace_uri} to alias #{desired_alias}. #{current_namespace_uri} is already mapped to alias #{desired_alias}"
486 end
487
488 # Check that desired_alias does not contain a period as per the
489 # spec.
490 if desired_alias.is_a?(String)
491 Util.assert(desired_alias.index('.').nil?,
492 "#{desired_alias} must not contain a dot")
493 end
494
495 # check that there is not already a (different) alias for this
496 # namespace URI.
497 _alias = @namespace_to_alias[namespace_uri]
498 if _alias and _alias != desired_alias
499 raise IndexError, "Cannot map #{namespace_uri} to alias #{desired_alias}. It is already mapped to alias #{_alias}"
500 end
501
502 @alias_to_namespace[desired_alias] = namespace_uri
503 @namespace_to_alias[namespace_uri] = desired_alias
504 @implicit_namespaces << namespace_uri if implicit
505 return desired_alias
506 end
507
508 # Add this namespace URI to the mapping, without caring what alias
509 # it ends up with.
510 def add(namespace_uri)
511 # see if this namepace is already mapped to an alias
512 _alias = @namespace_to_alias[namespace_uri]
513 return _alias if _alias
514
515 # Fall back to generating a numberical alias
516 i = 0
517 while true
518 _alias = 'ext' + i.to_s
519 begin
520 add_alias(namespace_uri, _alias)
521 rescue IndexError
522 i += 1
523 else
524 return _alias
525 end
526 end
527
528 raise StandardError, 'Unreachable'
529 end
530
531 def member?(namespace_uri)
532 @namespace_to_alias.has_key?(namespace_uri)
533 end
534
535 def each
536 @namespace_to_alias.each {|k,v| yield k,v}
537 end
538
539 def namespace_uris
540 # Return an iterator over the namespace URIs
541 return @namespace_to_alias.keys()
542 end
543
544 def implicit?(namespace_uri)
545 return @implicit_namespaces.member?(namespace_uri)
546 end
547
548 def aliases
549 # Return an iterator over the aliases
550 return @alias_to_namespace.keys()
551 end
552 end
553 end
Generated using the rcov code coverage analysis tool for Ruby version 0.7.0.