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