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 openid.oidutil import importElementTree
25 ElementTree = importElementTree()
26
27
28 try:
29 XMLTreeBuilder = ElementTree.XMLTreeBuilder
30
31
32 p = XMLTreeBuilder()
33 except ImportError:
34 from elementtree.SimpleXMLTreeBuilder import TreeBuilder as XMLTreeBuilder
35
36
37
38
39
40 try:
41
42
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
54 """An error with the XRDS document."""
55
56
57 reason = None
58
59
60
62 """Raised when there's an assertion in the XRDS that it does not have
63 the authority to make.
64 """
65
66
67
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
95 return '{%s}%s' % (ns, t)
96
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
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
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
121 canonicalID_tag = mkXRDTag('CanonicalID')
122
124 """Is this document an XRDS document?"""
125 root = xrd_tree.getroot()
126 return root.tag == root_tag
127
129 """Return the XRD element that should contain the Yadis services"""
130 xrd = None
131
132
133
134 for xrd in xrd_tree.findall(xrd_tag):
135 pass
136
137
138
139 if xrd is None:
140 raise XRDSError('No XRD present in tree')
141
142 return xrd
143
144
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
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
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."""
191 if other is self:
192 return 0
193
194 return 1
195
196 Max = _Max()
197
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
214 return Max
215
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
227 """Sort a list of elements that have priority attributes"""
228
229
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
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
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
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
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
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