1 """Extension argument processing code
2 """
3 __all__ = ['Message', 'NamespaceMap', 'no_default', 'registerNamespaceAlias',
4 'OPENID_NS', 'BARE_NS', 'OPENID1_NS', 'OPENID2_NS', 'SREG_URI',
5 'IDENTIFIER_SELECT']
6
7 import copy
8 import warnings
9 import urllib
10
11 from openid import oidutil
12 from openid import kvform
13 try:
14 ElementTree = oidutil.importElementTree()
15 except ImportError:
16
17
18 ElementTree = None
19
20
21 IDENTIFIER_SELECT = 'http://specs.openid.net/auth/2.0/identifier_select'
22
23
24
25 SREG_URI = 'http://openid.net/sreg/1.0'
26
27
28 OPENID1_NS = 'http://openid.net/signon/1.0'
29 THE_OTHER_OPENID1_NS = 'http://openid.net/signon/1.1'
30
31 OPENID1_NAMESPACES = OPENID1_NS, THE_OTHER_OPENID1_NS
32
33
34 OPENID2_NS = 'http://specs.openid.net/auth/2.0'
35
36
37
38 NULL_NAMESPACE = oidutil.Symbol('Null namespace')
39
40
41 OPENID_NS = oidutil.Symbol('OpenID namespace')
42
43
44
45 BARE_NS = oidutil.Symbol('Bare namespace')
46
47
48
49 OPENID1_URL_LIMIT = 2047
50
51
52 OPENID_PROTOCOL_FIELDS = [
53 'ns', 'mode', 'error', 'return_to', 'contact', 'reference',
54 'signed', 'assoc_type', 'session_type', 'dh_modulus', 'dh_gen',
55 'dh_consumer_public', 'claimed_id', 'identity', 'realm',
56 'invalidate_handle', 'op_endpoint', 'response_nonce', 'sig',
57 'assoc_handle', 'trust_root', 'openid',
58 ]
59
61 """Raised if the generic OpenID namespace is accessed when there
62 is no OpenID namespace set for this message."""
63
65 """Raised if openid.ns is not a recognized value.
66
67 For recognized values, see L{Message.allowed_openid_namespaces}
68 """
70 s = "Invalid OpenID Namespace"
71 if self.args:
72 s += " %r" % (self.args[0],)
73 return s
74
75
76
77
78 no_default = object()
79
80
81
82 registered_aliases = {}
83
85 """
86 Raised when an alias or namespace URI has already been registered.
87 """
88 pass
89
112
114 """
115 In the implementation of this object, None represents the global
116 namespace as well as a namespace with no key.
117
118 @cvar namespaces: A dictionary specifying specific
119 namespace-URI to alias mappings that should be used when
120 generating namespace aliases.
121
122 @ivar ns_args: two-level dictionary of the values in this message,
123 grouped by namespace URI. The first level is the namespace
124 URI.
125 """
126
127 allowed_openid_namespaces = [OPENID1_NS, THE_OTHER_OPENID1_NS, OPENID2_NS]
128
129 - def __init__(self, openid_namespace=None):
130 """Create an empty Message.
131
132 @raises InvalidOpenIDNamespace: if openid_namespace is not in
133 L{Message.allowed_openid_namespaces}
134 """
135 self.args = {}
136 self.namespaces = NamespaceMap()
137 if openid_namespace is None:
138 self._openid_ns_uri = None
139 else:
140 implicit = openid_namespace in OPENID1_NAMESPACES
141 self.setOpenIDNamespace(openid_namespace, implicit)
142
143 - def fromPostArgs(cls, args):
144 """Construct a Message containing a set of POST arguments.
145
146 """
147 self = cls()
148
149
150 openid_args = {}
151 for key, value in args.items():
152 if isinstance(value, list):
153 raise TypeError("query dict must have one value for each key, "
154 "not lists of values. Query is %r" % (args,))
155
156
157 try:
158 prefix, rest = key.split('.', 1)
159 except ValueError:
160 prefix = None
161
162 if prefix != 'openid':
163 self.args[(BARE_NS, key)] = value
164 else:
165 openid_args[rest] = value
166
167 self._fromOpenIDArgs(openid_args)
168
169 return self
170
171 fromPostArgs = classmethod(fromPostArgs)
172
174 """Construct a Message from a parsed KVForm message.
175
176 @raises InvalidOpenIDNamespace: if openid.ns is not in
177 L{Message.allowed_openid_namespaces}
178 """
179 self = cls()
180 self._fromOpenIDArgs(openid_args)
181 return self
182
183 fromOpenIDArgs = classmethod(fromOpenIDArgs)
184
186 ns_args = []
187
188
189 for rest, value in openid_args.iteritems():
190 try:
191 ns_alias, ns_key = rest.split('.', 1)
192 except ValueError:
193 ns_alias = NULL_NAMESPACE
194 ns_key = rest
195
196 if ns_alias == 'ns':
197 self.namespaces.addAlias(value, ns_key)
198 elif ns_alias == NULL_NAMESPACE and ns_key == 'ns':
199
200 self.setOpenIDNamespace(value, False)
201 else:
202 ns_args.append((ns_alias, ns_key, value))
203
204
205 if not self.getOpenIDNamespace():
206 self.setOpenIDNamespace(OPENID1_NS, True)
207
208
209 for (ns_alias, ns_key, value) in ns_args:
210 ns_uri = self.namespaces.getNamespaceURI(ns_alias)
211 if ns_uri is None:
212
213 ns_uri = self._getDefaultNamespace(ns_alias)
214 if ns_uri is None:
215 ns_uri = self.getOpenIDNamespace()
216 ns_key = '%s.%s' % (ns_alias, ns_key)
217 else:
218 self.namespaces.addAlias(ns_uri, ns_alias, implicit=True)
219
220 self.setArg(ns_uri, ns_key, value)
221
232
234 """Set the OpenID namespace URI used in this message.
235
236 @raises InvalidOpenIDNamespace: if the namespace is not in
237 L{Message.allowed_openid_namespaces}
238 """
239 if openid_ns_uri not in self.allowed_openid_namespaces:
240 raise InvalidOpenIDNamespace(openid_ns_uri)
241
242 self.namespaces.addAlias(openid_ns_uri, NULL_NAMESPACE, implicit)
243 self._openid_ns_uri = openid_ns_uri
244
246 return self._openid_ns_uri
247
250
253
257
258 fromKVForm = classmethod(fromKVForm)
259
261 return copy.deepcopy(self)
262
263 - def toPostArgs(self):
264 """Return all arguments with openid. in front of namespaced arguments.
265 """
266 args = {}
267
268
269 for ns_uri, alias in self.namespaces.iteritems():
270 if self.namespaces.isImplicit(ns_uri):
271 continue
272 if alias == NULL_NAMESPACE:
273 ns_key = 'openid.ns'
274 else:
275 ns_key = 'openid.ns.' + alias
276 args[ns_key] = ns_uri
277
278 for (ns_uri, ns_key), value in self.args.iteritems():
279 key = self.getKey(ns_uri, ns_key)
280 args[key] = value.encode('UTF-8')
281
282 return args
283
285 """Return all namespaced arguments, failing if any
286 non-namespaced arguments exist."""
287
288 post_args = self.toPostArgs()
289 kvargs = {}
290 for k, v in post_args.iteritems():
291 if not k.startswith('openid.'):
292 raise ValueError(
293 'This message can only be encoded as a POST, because it '
294 'contains arguments that are not prefixed with "openid."')
295 else:
296 kvargs[k[7:]] = v
297
298 return kvargs
299
349
350 - def toURL(self, base_url):
354
361
363 """Generate an x-www-urlencoded string"""
364 args = self.toPostArgs().items()
365 args.sort()
366 return urllib.urlencode(args)
367
369 """Convert an input value into the internally used values of
370 this object
371
372 @param namespace: The string or constant to convert
373 @type namespace: str or unicode or BARE_NS or OPENID_NS
374 """
375 if namespace == OPENID_NS:
376 if self._openid_ns_uri is None:
377 raise UndefinedOpenIDNamespace('OpenID namespace not set')
378 else:
379 namespace = self._openid_ns_uri
380
381 if namespace != BARE_NS and type(namespace) not in [str, unicode]:
382 raise TypeError(
383 "Namespace must be BARE_NS, OPENID_NS or a string. got %r"
384 % (namespace,))
385
386 if namespace != BARE_NS and ':' not in namespace:
387 fmt = 'OpenID 2.0 namespace identifiers SHOULD be URIs. Got %r'
388 warnings.warn(fmt % (namespace,), DeprecationWarning)
389
390 if namespace == 'sreg':
391 fmt = 'Using %r instead of "sreg" as namespace'
392 warnings.warn(fmt % (SREG_URI,), DeprecationWarning,)
393 return SREG_URI
394
395 return namespace
396
397 - def hasKey(self, namespace, ns_key):
400
401 - def getKey(self, namespace, ns_key):
419
420 - def getArg(self, namespace, key, default=None):
421 """Get a value for a namespaced key.
422
423 @param namespace: The namespace in the message for this key
424 @type namespace: str
425
426 @param key: The key to get within this namespace
427 @type key: str
428
429 @param default: The value to use if this key is absent from
430 this message. Using the special value
431 openid.message.no_default will result in this method
432 raising a KeyError instead of returning the default.
433
434 @rtype: str or the type of default
435 @raises KeyError: if default is no_default
436 @raises UndefinedOpenIDNamespace: if the message has not yet
437 had an OpenID namespace set
438 """
439 namespace = self._fixNS(namespace)
440 args_key = (namespace, key)
441 try:
442 return self.args[args_key]
443 except KeyError:
444 if default is no_default:
445 raise KeyError((namespace, key))
446 else:
447 return default
448
450 """Get the arguments that are defined for this namespace URI
451
452 @returns: mapping from namespaced keys to values
453 @returntype: dict
454 """
455 namespace = self._fixNS(namespace)
456 return dict([
457 (ns_key, value)
458 for ((pair_ns, ns_key), value)
459 in self.args.iteritems()
460 if pair_ns == namespace
461 ])
462
464 """Set multiple key/value pairs in one call
465
466 @param updates: The values to set
467 @type updates: {unicode:unicode}
468 """
469 namespace = self._fixNS(namespace)
470 for k, v in updates.iteritems():
471 self.setArg(namespace, k, v)
472
473 - def setArg(self, namespace, key, value):
481
482 - def delArg(self, namespace, key):
485
487 return "<%s.%s %r>" % (self.__class__.__module__,
488 self.__class__.__name__,
489 self.args)
490
492 return self.args == other.args
493
494
496 return not (self == other)
497
498
500 if aliased_key == 'ns':
501 return self.getOpenIDNamespace()
502
503 if aliased_key.startswith('ns.'):
504 uri = self.namespaces.getNamespaceURI(aliased_key[3:])
505 if uri is None:
506 if default == no_default:
507 raise KeyError
508 else:
509 return default
510 else:
511 return uri
512
513 try:
514 alias, key = aliased_key.split('.', 1)
515 except ValueError:
516
517 ns = None
518 else:
519 ns = self.namespaces.getNamespaceURI(alias)
520
521 if ns is None:
522 key = aliased_key
523 ns = self.getOpenIDNamespace()
524
525 return self.getArg(ns, key, default)
526
528 """Maintains a bijective map between namespace uris and aliases.
529 """
531 self.alias_to_namespace = {}
532 self.namespace_to_alias = {}
533 self.implicit_namespaces = []
534
536 return self.namespace_to_alias.get(namespace_uri)
537
539 return self.alias_to_namespace.get(alias)
540
542 """Return an iterator over the namespace URIs"""
543 return iter(self.namespace_to_alias)
544
546 """Return an iterator over the aliases"""
547 return iter(self.alias_to_namespace)
548
550 """Iterate over the mapping
551
552 @returns: iterator of (namespace_uri, alias)
553 """
554 return self.namespace_to_alias.iteritems()
555
556 - def addAlias(self, namespace_uri, desired_alias, implicit=False):
557 """Add an alias from this namespace URI to the desired alias
558 """
559
560
561 assert desired_alias not in OPENID_PROTOCOL_FIELDS, \
562 "%r is not an allowed namespace alias" % (desired_alias,)
563
564
565
566 if type(desired_alias) in [str, unicode]:
567 assert '.' not in desired_alias, \
568 "%r must not contain a dot" % (desired_alias,)
569
570
571
572 current_namespace_uri = self.alias_to_namespace.get(desired_alias)
573 if (current_namespace_uri is not None
574 and current_namespace_uri != namespace_uri):
575
576 fmt = ('Cannot map %r to alias %r. '
577 '%r is already mapped to alias %r')
578
579 msg = fmt % (
580 namespace_uri,
581 desired_alias,
582 current_namespace_uri,
583 desired_alias)
584 raise KeyError(msg)
585
586
587
588 alias = self.namespace_to_alias.get(namespace_uri)
589 if alias is not None and alias != desired_alias:
590 fmt = ('Cannot map %r to alias %r. '
591 'It is already mapped to alias %r')
592 raise KeyError(fmt % (namespace_uri, desired_alias, alias))
593
594 assert (desired_alias == NULL_NAMESPACE or
595 type(desired_alias) in [str, unicode]), repr(desired_alias)
596 assert namespace_uri not in self.implicit_namespaces
597 self.alias_to_namespace[desired_alias] = namespace_uri
598 self.namespace_to_alias[namespace_uri] = desired_alias
599 if implicit:
600 self.implicit_namespaces.append(namespace_uri)
601 return desired_alias
602
603 - def add(self, namespace_uri):
604 """Add this namespace URI to the mapping, without caring what
605 alias it ends up with"""
606
607 alias = self.namespace_to_alias.get(namespace_uri)
608 if alias is not None:
609 return alias
610
611
612 i = 0
613 while True:
614 alias = 'ext' + str(i)
615 try:
616 self.addAlias(namespace_uri, alias)
617 except KeyError:
618 i += 1
619 else:
620 return alias
621
622 assert False, "Not reached"
623
625 return namespace_uri in self.namespace_to_alias
626
629
631 return namespace_uri in self.implicit_namespaces
632