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
91 """
92 Registers a (namespace URI, alias) mapping in a global namespace
93 alias map. Raises NamespaceAliasRegistrationError if either the
94 namespace URI or alias has already been registered with a
95 different value. This function is required if you want to use a
96 namespace with an OpenID 1 message.
97 """
98 global registered_aliases
99
100 if registered_aliases.get(alias) == namespace_uri:
101 return
102
103 if namespace_uri in registered_aliases.values():
104 raise NamespaceAliasRegistrationError, \
105 'Namespace uri %r already registered' % (namespace_uri,)
106
107 if alias in registered_aliases:
108 raise NamespaceAliasRegistrationError, \
109 'Alias %r already registered' % (alias,)
110
111 registered_aliases[alias] = namespace_uri
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):
351 """Generate a GET URL with the parameters in this message
352 attached as query parameters."""
353 return oidutil.appendArgs(base_url, self.toPostArgs())
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