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