1
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
31 try:
32 XMLTreeBuilder = ElementTree.XMLTreeBuilder
33
34
35 p = XMLTreeBuilder()
36 except ImportError:
37 from elementtree.SimpleXMLTreeBuilder import TreeBuilder as XMLTreeBuilder
38
39
40
41
42
43 try:
44
45
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
57 """An error with the XRDS document."""
58
59
60 reason = None
61
62
63
65 """Raised when there's an assertion in the XRDS that it does not have
66 the authority to make.
67 """
68
69
70
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
98 return '{%s}%s' % (ns, t)
99
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
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
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
125 canonicalID_tag = mkXRDTag('CanonicalID')
126
128 """Is this document an XRDS document?"""
129 root = xrd_tree.getroot()
130 return root.tag == root_tag
131
133 """Return the XRD element that should contain the Yadis services"""
134 xrd = None
135
136
137
138 for xrd in xrd_tree.findall(xrd_tag):
139 pass
140
141
142
143 if xrd is None:
144 raise XRDSError('No XRD present in tree')
145
146 return xrd
147
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
169 expires_time = strptime(expires_string, "%Y-%m-%dT%H:%M:%SZ")
170 return datetime(*expires_time[0:6])
171
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
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
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."""
218 if other is self:
219 return 0
220
221 return 1
222
223 Max = _Max()
224
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
241 return Max
242
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
254 """Sort a list of elements that have priority attributes"""
255
256
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
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
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
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
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
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