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