Package openid :: Package yadis :: Module etxrd
[frames] | no frames]

Source Code for Module openid.yadis.etxrd

  1  # -*- test-case-name: yadis.test.test_etxrd -*- 
  2  """ 
  3  ElementTree interface to an XRD document. 
  4  """ 
  5   
  6  __all__ = [ 
  7      'nsTag', 
  8      'mkXRDTag', 
  9      'isXRDS', 
 10      'parseXRDS', 
 11      'getCanonicalID', 
 12      'getYadisXRD', 
 13      'getPriorityStrict', 
 14      'getPriority', 
 15      'prioSort', 
 16      'iterServices', 
 17      'expandService', 
 18      'expandServices', 
 19      ] 
 20   
 21  import sys 
 22  import random 
 23   
 24  from openid.oidutil import importElementTree 
 25  ElementTree = importElementTree() 
 26   
 27  # Use expat if it's present. Otherwise, use xmllib 
 28  try: 
 29      XMLTreeBuilder = ElementTree.XMLTreeBuilder 
 30   
 31      # This will raise an ImportError if an XML parser is not present. 
 32      p = XMLTreeBuilder() 
 33  except ImportError: 
 34      from elementtree.SimpleXMLTreeBuilder import TreeBuilder as XMLTreeBuilder 
 35   
 36  # the different elementtree modules don't have a common exception 
 37  # model. We just want to be able to catch the exceptions that signify 
 38  # malformed XML data and wrap them, so that the other library code 
 39  # doesn't have to know which XML library we're using. 
 40  try: 
 41      # Make the parser raise an exception so we can sniff out the type 
 42      # of exceptions 
 43      p.feed('> purposely malformed XML <') 
 44      p.close() 
 45  except (SystemExit, MemoryError, AssertionError, ImportError): 
 46      raise 
 47  except: 
 48      XMLError = sys.exc_info()[0] 
 49      del p 
 50   
 51  from openid.yadis import xri 
 52   
53 -class XRDSError(Exception):
54 """An error with the XRDS document.""" 55 56 # The exception that triggered this exception 57 reason = None
58 59 60
61 -class XRDSFraud(XRDSError):
62 """Raised when there's an assertion in the XRDS that it does not have 63 the authority to make. 64 """
65 66 67
68 -def parseXRDS(text):
69 """Parse the given text as an XRDS document. 70 71 @return: ElementTree containing an XRDS document 72 73 @raises XRDSError: When there is a parse error or the document does 74 not contain an XRDS. 75 """ 76 try: 77 parser = XMLTreeBuilder() 78 parser.feed(text) 79 element = parser.close() 80 except XMLError, why: 81 exc = XRDSError('Error parsing document as XML') 82 exc.reason = why 83 raise exc 84 else: 85 tree = ElementTree.ElementTree(element) 86 if not isXRDS(tree): 87 raise XRDSError('Not an XRDS document') 88 89 return tree
90 91 XRD_NS_2_0 = 'xri://$xrd*($v*2.0)' 92 XRDS_NS = 'xri://$xrds' 93
94 -def nsTag(ns, t):
95 return '{%s}%s' % (ns, t)
96
97 -def mkXRDTag(t):
98 """basestring -> basestring 99 100 Create a tag name in the XRD 2.0 XML namespace suitable for using 101 with ElementTree 102 """ 103 return nsTag(XRD_NS_2_0, t)
104
105 -def mkXRDSTag(t):
106 """basestring -> basestring 107 108 Create a tag name in the XRDS XML namespace suitable for using 109 with ElementTree 110 """ 111 return nsTag(XRDS_NS, t)
112 113 # Tags that are used in Yadis documents 114 root_tag = mkXRDSTag('XRDS') 115 service_tag = mkXRDTag('Service') 116 xrd_tag = mkXRDTag('XRD') 117 type_tag = mkXRDTag('Type') 118 uri_tag = mkXRDTag('URI') 119 120 # Other XRD tags 121 canonicalID_tag = mkXRDTag('CanonicalID') 122
123 -def isXRDS(xrd_tree):
124 """Is this document an XRDS document?""" 125 root = xrd_tree.getroot() 126 return root.tag == root_tag
127
128 -def getYadisXRD(xrd_tree):
129 """Return the XRD element that should contain the Yadis services""" 130 xrd = None 131 132 # for the side-effect of assigning the last one in the list to the 133 # xrd variable 134 for xrd in xrd_tree.findall(xrd_tag): 135 pass 136 137 # There were no elements found, or else xrd would be set to the 138 # last one 139 if xrd is None: 140 raise XRDSError('No XRD present in tree') 141 142 return xrd
143 144
145 -def getCanonicalID(iname, xrd_tree):
146 """Return the CanonicalID from this XRDS document. 147 148 @param iname: the XRI being resolved. 149 @type iname: unicode 150 151 @param xrd_tree: The XRDS output from the resolver. 152 @type xrd_tree: ElementTree 153 154 @returns: The XRI CanonicalID or None. 155 @returntype: unicode or None 156 """ 157 xrd_list = xrd_tree.findall(xrd_tag) 158 xrd_list.reverse() 159 160 try: 161 canonicalID = xri.XRI(xrd_list[0].findall(canonicalID_tag)[-1].text) 162 except IndexError: 163 return None 164 165 childID = canonicalID 166 167 for xrd in xrd_list[1:]: 168 # XXX: can't use rsplit until we require python >= 2.4. 169 parent_sought = childID[:childID.rindex('!')] 170 parent_list = [xri.XRI(c.text) for c in xrd.findall(canonicalID_tag)] 171 if parent_sought not in parent_list: 172 raise XRDSFraud("%r can not come from any of %s" % (parent_sought, 173 parent_list)) 174 175 childID = parent_sought 176 177 root = xri.rootAuthority(iname) 178 if not xri.providerIsAuthoritative(root, childID): 179 raise XRDSFraud("%r can not come from root %r" % (childID, root)) 180 181 return canonicalID
182 183 184
185 -class _Max(object):
186 """Value that compares greater than any other value. 187 188 Should only be used as a singleton. Implemented for use as a 189 priority value for when a priority is not specified."""
190 - def __cmp__(self, other):
191 if other is self: 192 return 0 193 194 return 1
195 196 Max = _Max() 197
198 -def getPriorityStrict(element):
199 """Get the priority of this element. 200 201 Raises ValueError if the value of the priority is invalid. If no 202 priority is specified, it returns a value that compares greater 203 than any other value. 204 """ 205 prio_str = element.get('priority') 206 if prio_str is not None: 207 prio_val = int(prio_str) 208 if prio_val >= 0: 209 return prio_val 210 else: 211 raise ValueError('Priority values must be non-negative integers') 212 213 # Any errors in parsing the priority fall through to here 214 return Max
215
216 -def getPriority(element):
217 """Get the priority of this element 218 219 Returns Max if no priority is specified or the priority value is invalid. 220 """ 221 try: 222 return getPriorityStrict(element) 223 except ValueError: 224 return Max
225
226 -def prioSort(elements):
227 """Sort a list of elements that have priority attributes""" 228 # Randomize the services before sorting so that equal priority 229 # elements are load-balanced. 230 random.shuffle(elements) 231 232 prio_elems = [(getPriority(e), e) for e in elements] 233 prio_elems.sort() 234 sorted_elems = [s for (_, s) in prio_elems] 235 return sorted_elems
236
237 -def iterServices(xrd_tree):
238 """Return an iterable over the Service elements in the Yadis XRD 239 240 sorted by priority""" 241 xrd = getYadisXRD(xrd_tree) 242 return prioSort(xrd.findall(service_tag))
243
244 -def sortedURIs(service_element):
245 """Given a Service element, return a list of the contents of all 246 URI tags in priority order.""" 247 return [uri_element.text for uri_element 248 in prioSort(service_element.findall(uri_tag))]
249
250 -def getTypeURIs(service_element):
251 """Given a Service element, return a list of the contents of all 252 Type tags""" 253 return [type_element.text for type_element 254 in service_element.findall(type_tag)]
255
256 -def expandService(service_element):
257 """Take a service element and expand it into an iterator of: 258 ([type_uri], uri, service_element) 259 """ 260 uris = sortedURIs(service_element) 261 if not uris: 262 uris = [None] 263 264 expanded = [] 265 for uri in uris: 266 type_uris = getTypeURIs(service_element) 267 expanded.append((type_uris, uri, service_element)) 268 269 return expanded
270
271 -def expandServices(service_elements):
272 """Take a sorted iterator of service elements and expand it into a 273 sorted iterator of: 274 ([type_uri], uri, service_element) 275 276 There may be more than one item in the resulting list for each 277 service element if there is more than one URI or type for a 278 service, but each triple will be unique. 279 280 If there is no URI or Type for a Service element, it will not 281 appear in the result. 282 """ 283 expanded = [] 284 for service_element in service_elements: 285 expanded.extend(expandService(service_element)) 286 287 return expanded
288