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
32
33
34 try:
35
36
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
46 """An error with the XRDS document."""
47
48
49 reason = None
50
51
52
54 """Raised when there's an assertion in the XRDS that it does not have
55 the authority to make.
56 """
57
58
59
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
85 return '{%s}%s' % (ns, t)
86
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
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
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
112 canonicalID_tag = mkXRDTag('CanonicalID')
113
115 """Is this document an XRDS document?"""
116 root = xrd_tree.getroot()
117 return root.tag == root_tag
118
120 """Return the XRD element that should contain the Yadis services"""
121 xrd = None
122
123
124
125 for xrd in xrd_tree.findall(xrd_tag):
126 pass
127
128
129
130 if xrd is None:
131 raise XRDSError('No XRD present in tree')
132
133 return xrd
134
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
156 expires_time = strptime(expires_string, "%Y-%m-%dT%H:%M:%SZ")
157 return datetime(*expires_time[0:6])
158
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
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
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."""
204 if other is self:
205 return 0
206
207 return 1
208
209 Max = _Max()
210
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
227 return Max
228
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
240 """Sort a list of elements that have priority attributes"""
241
242
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
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
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
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
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
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