1 """Extension argument processing code
2 """
3 __all__ = ['Message', 'NamespaceMap', 'no_default',
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
30
31 OPENID2_NS = 'http://specs.openid.net/auth/2.0'
32
33
34
35 NULL_NAMESPACE = oidutil.Symbol('Null namespace')
36
37
38 OPENID_NS = oidutil.Symbol('OpenID namespace')
39
40
41
42 BARE_NS = oidutil.Symbol('Bare namespace')
43
44
45 OPENID_PROTOCOL_FIELDS = [
46 'ns', 'mode', 'error', 'return_to', 'contact', 'reference',
47 'signed', 'assoc_type', 'session_type', 'dh_modulus', 'dh_gen',
48 'dh_consumer_public', 'claimed_id', 'identity', 'realm',
49 'invalidate_handle', 'op_endpoint', 'response_nonce', 'sig',
50 'assoc_handle', 'trust_root', 'openid',
51 ]
52
54 """Raised if the generic OpenID namespace is accessed when there
55 is no OpenID namespace set for this message."""
56
57
58
59 no_default = object()
60
61
62
63 registered_aliases = {}
64
66 """
67 Raised when an alias or namespace URI has already been registered.
68 """
69 pass
70
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 """
79 global registered_aliases
80
81 if registered_aliases.get(alias) == namespace_uri:
82 return
83
84 if namespace_uri in registered_aliases.values():
85 raise NamespaceAliasRegistrationError, \
86 'Namespace uri %r already registered' % (namespace_uri,)
87
88 if alias in registered_aliases:
89 raise NamespaceAliasRegistrationError, \
90 'Alias %r already registered' % (alias,)
91
92 registered_aliases[alias] = namespace_uri
93
95 """
96 In the implementation of this object, None represents the global
97 namespace as well as a namespace with no key.
98
99 @cvar namespaces: A dictionary specifying specific
100 namespace-URI to alias mappings that should be used when
101 generating namespace aliases.
102
103 @ivar ns_args: two-level dictionary of the values in this message,
104 grouped by namespace URI. The first level is the namespace
105 URI.
106 """
107
108 allowed_openid_namespaces = [OPENID1_NS, OPENID2_NS]
109
110 - def __init__(self, openid_namespace=None):
111 """Create an empty Message"""
112 self.args = {}
113 self.namespaces = NamespaceMap()
114 if openid_namespace is None:
115 self._openid_ns_uri = None
116 else:
117 self.setOpenIDNamespace(openid_namespace)
118
119 - def fromPostArgs(cls, args):
120 """Construct a Message containing a set of POST arguments"""
121 self = cls()
122
123
124 openid_args = {}
125 for key, value in args.iteritems():
126 if isinstance(value, list):
127 raise TypeError("query dict must have one value for each key, "
128 "not lists of values. Query is %r" % (args,))
129
130
131 try:
132 prefix, rest = key.split('.', 1)
133 except ValueError:
134 prefix = None
135
136 if prefix != 'openid':
137 self.args[(BARE_NS, key)] = value
138 else:
139 openid_args[rest] = value
140
141 self._fromOpenIDArgs(openid_args)
142
143 return self
144
145 fromPostArgs = classmethod(fromPostArgs)
146
148 """Construct a Message from a parsed KVForm message"""
149 self = cls()
150 self._fromOpenIDArgs(openid_args)
151 return self
152
153 fromOpenIDArgs = classmethod(fromOpenIDArgs)
154
156 global registered_aliases
157
158 ns_args = []
159
160
161 for rest, value in openid_args.iteritems():
162 try:
163 ns_alias, ns_key = rest.split('.', 1)
164 except ValueError:
165 ns_alias = NULL_NAMESPACE
166 ns_key = rest
167
168 if ns_alias == 'ns':
169 self.namespaces.addAlias(value, ns_key)
170 elif ns_alias == NULL_NAMESPACE and ns_key == 'ns':
171
172 self.namespaces.addAlias(value, NULL_NAMESPACE)
173 else:
174 ns_args.append((ns_alias, ns_key, value))
175
176
177 openid_ns_uri = self.namespaces.getNamespaceURI(NULL_NAMESPACE)
178 if openid_ns_uri is None:
179 openid_ns_uri = OPENID1_NS
180
181 self.setOpenIDNamespace(openid_ns_uri)
182
183
184 for (ns_alias, ns_key, value) in ns_args:
185 ns_uri = self.namespaces.getNamespaceURI(ns_alias)
186 if ns_uri is None:
187
188
189 if openid_ns_uri == OPENID1_NS:
190 for _alias, _uri in registered_aliases.iteritems():
191 if _alias == ns_alias:
192 ns_uri = _uri
193 break
194
195 if ns_uri is None:
196 ns_uri = openid_ns_uri
197 ns_key = '%s.%s' % (ns_alias, ns_key)
198 else:
199 self.namespaces.addAlias(ns_uri, ns_alias)
200
201 self.setArg(ns_uri, ns_key, value)
202
209
211 return self._openid_ns_uri
212
215
218
222
223 fromKVForm = classmethod(fromKVForm)
224
226 return copy.deepcopy(self)
227
228 - def toPostArgs(self):
229 """Return all arguments with openid. in front of namespaced arguments.
230 """
231 args = {}
232
233
234 for ns_uri, alias in self.namespaces.iteritems():
235 if alias == NULL_NAMESPACE:
236 if ns_uri != OPENID1_NS:
237 args['openid.ns'] = ns_uri
238 else:
239
240
241
242
243
244
245
246 pass
247 else:
248 if self.getOpenIDNamespace() != OPENID1_NS:
249 ns_key = 'openid.ns.' + alias
250 args[ns_key] = ns_uri
251
252 for (ns_uri, ns_key), value in self.args.iteritems():
253 key = self.getKey(ns_uri, ns_key)
254 args[key] = value
255
256 return args
257
259 """Return all namespaced arguments, failing if any
260 non-namespaced arguments exist."""
261
262 post_args = self.toPostArgs()
263 kvargs = {}
264 for k, v in post_args.iteritems():
265 if not k.startswith('openid.'):
266 raise ValueError(
267 'This message can only be encoded as a POST, because it '
268 'contains arguments that are not prefixed with "openid."')
269 else:
270 kvargs[k[7:]] = v
271
272 return kvargs
273
321
322 - def toURL(self, base_url):
323 """Generate a GET URL with the parameters in this message
324 attached as query parameters."""
325 return oidutil.appendArgs(base_url, self.toPostArgs())
326
333
335 """Generate an x-www-urlencoded string"""
336 args = self.toPostArgs().items()
337 args.sort()
338 return urllib.urlencode(args)
339
341 """Convert an input value into the internally used values of
342 this object
343
344 @param namespace: The string or constant to convert
345 @type namespace: str or unicode or BARE_NS or OPENID_NS
346 """
347 if namespace == OPENID_NS:
348 if self._openid_ns_uri is None:
349 raise UndefinedOpenIDNamespace('OpenID namespace not set')
350 else:
351 namespace = self._openid_ns_uri
352
353 if namespace != BARE_NS and type(namespace) not in [str, unicode]:
354 raise TypeError(
355 "Namespace must be BARE_NS, OPENID_NS or a string. got %r"
356 % (namespace,))
357
358 if namespace != BARE_NS and ':' not in namespace:
359 fmt = 'OpenID 2.0 namespace identifiers SHOULD be URIs. Got %r'
360 warnings.warn(fmt % (namespace,), DeprecationWarning)
361
362 if namespace == 'sreg':
363 fmt = 'Using %r instead of "sreg" as namespace'
364 warnings.warn(fmt % (SREG_URI,), DeprecationWarning,)
365 return SREG_URI
366
367 return namespace
368
369 - def hasKey(self, namespace, ns_key):
370 namespace = self._fixNS(namespace)
371 return (namespace, ns_key) in self.args
372
373 - def getKey(self, namespace, ns_key):
374 """Get the key for a particular namespaced argument"""
375 namespace = self._fixNS(namespace)
376 if namespace == BARE_NS:
377 return ns_key
378
379 ns_alias = self.namespaces.getAlias(namespace)
380
381
382 if ns_alias is None:
383 return None
384
385 if ns_alias == NULL_NAMESPACE:
386 tail = ns_key
387 else:
388 tail = '%s.%s' % (ns_alias, ns_key)
389
390 return 'openid.' + tail
391
392 - def getArg(self, namespace, key, default=None):
393 """Get a value for a namespaced key.
394
395 @param namespace: The namespace in the message for this key
396 @type namespace: str
397
398 @param key: The key to get within this namespace
399 @type key: str
400
401 @param default: The value to use if this key is absent from
402 this message. Using the special value
403 openid.message.no_default will result in this method
404 raising a KeyError instead of returning the default.
405
406 @rtype: str or the type of default
407 @raises KeyError: if default is no_default
408 @raises UndefinedOpenIDNamespace: if the message has not yet
409 had an OpenID namespace set
410 """
411 namespace = self._fixNS(namespace)
412 args_key = (namespace, key)
413 try:
414 return self.args[args_key]
415 except KeyError:
416 if default is no_default:
417 raise KeyError((namespace, key))
418 else:
419 return default
420
422 """Get the arguments that are defined for this namespace URI
423
424 @returns: mapping from namespaced keys to values
425 @returntype: dict
426 """
427 namespace = self._fixNS(namespace)
428 return dict([
429 (ns_key, value)
430 for ((pair_ns, ns_key), value)
431 in self.args.iteritems()
432 if pair_ns == namespace
433 ])
434
436 """Set multiple key/value pairs in one call
437
438 @param updates: The values to set
439 @type updates: {unicode:unicode}
440 """
441 namespace = self._fixNS(namespace)
442 for k, v in updates.iteritems():
443 self.setArg(namespace, k, v)
444
445 - def setArg(self, namespace, key, value):
446 """Set a single argument in this namespace"""
447 namespace = self._fixNS(namespace)
448 self.args[(namespace, key)] = value
449 if not (namespace is BARE_NS):
450 self.namespaces.add(namespace)
451
452 - def delArg(self, namespace, key):
453 namespace = self._fixNS(namespace)
454 del self.args[(namespace, key)]
455
457 return "<%s.%s %r>" % (self.__class__.__module__,
458 self.__class__.__name__,
459 self.args)
460
462 return self.args == other.args
463
464
466 return not (self == other)
467
468
470 try:
471 alias, key = aliased_key.split('.', 1)
472 except ValueError:
473
474 ns = None
475 else:
476 ns = self.namespaces.getNamespaceURI(alias)
477
478 if ns is None:
479 key = aliased_key
480 ns = self.getOpenIDNamespace()
481
482 return self.getArg(ns, key, default)
483
485 """Maintains a bijective map between namespace uris and aliases.
486 """
488 self.alias_to_namespace = {}
489 self.namespace_to_alias = {}
490
492 return self.namespace_to_alias.get(namespace_uri)
493
495 return self.alias_to_namespace.get(alias)
496
498 """Return an iterator over the namespace URIs"""
499 return iter(self.namespace_to_alias)
500
502 """Return an iterator over the aliases"""
503 return iter(self.alias_to_namespace)
504
506 """Iterate over the mapping
507
508 @returns: iterator of (namespace_uri, alias)
509 """
510 return self.namespace_to_alias.iteritems()
511
512 - def addAlias(self, namespace_uri, desired_alias):
513 """Add an alias from this namespace URI to the desired alias
514 """
515
516
517 assert desired_alias not in OPENID_PROTOCOL_FIELDS, \
518 "%r is not an allowed namespace alias" % (desired_alias,)
519
520
521
522 if type(desired_alias) in [str, unicode]:
523 assert '.' not in desired_alias, \
524 "%r must not contain a dot" % (desired_alias,)
525
526
527
528 current_namespace_uri = self.alias_to_namespace.get(desired_alias)
529 if (current_namespace_uri is not None
530 and current_namespace_uri != namespace_uri):
531
532 fmt = ('Cannot map %r to alias %r. '
533 '%r is already mapped to alias %r')
534
535 msg = fmt % (
536 namespace_uri,
537 desired_alias,
538 current_namespace_uri,
539 desired_alias)
540 raise KeyError(msg)
541
542
543
544 alias = self.namespace_to_alias.get(namespace_uri)
545 if alias is not None and alias != desired_alias:
546 fmt = ('Cannot map %r to alias %r. '
547 'It is already mapped to alias %r')
548 raise KeyError(fmt % (namespace_uri, desired_alias, alias))
549
550 assert (desired_alias == NULL_NAMESPACE or
551 type(desired_alias) in [str, unicode]), repr(desired_alias)
552 self.alias_to_namespace[desired_alias] = namespace_uri
553 self.namespace_to_alias[namespace_uri] = desired_alias
554 return desired_alias
555
556 - def add(self, namespace_uri):
557 """Add this namespace URI to the mapping, without caring what
558 alias it ends up with"""
559
560 alias = self.namespace_to_alias.get(namespace_uri)
561 if alias is not None:
562 return alias
563
564
565 i = 0
566 while True:
567 alias = 'ext' + str(i)
568 try:
569 self.addAlias(namespace_uri, alias)
570 except KeyError:
571 i += 1
572 else:
573 return alias
574
575 assert False, "Not reached"
576
578 return namespace_uri in self.namespace_to_alias
579
582