1 """
2 This module contains the C{L{TrustRoot}} class, which helps handle
3 trust root checking. This module is used by the
4 C{L{openid.server.server}} module, but it is also available to server
5 implementers who wish to use it for additional trust root checking.
6 """
7
8 from urlparse import urlparse, urlunparse
9
10
11 _protocols = ['http', 'https']
12 _top_level_domains = (
13 'com|edu|gov|int|mil|net|org|biz|info|name|museum|coop|aero|ac|ad|ae|'
14 'af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|az|ba|bb|bd|be|bf|bg|bh|bi|bj|'
15 'bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|'
16 'cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|fi|fj|fk|fm|fo|'
17 'fr|ga|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|'
18 'ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|'
19 'kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|mg|mh|mk|ml|mm|'
20 'mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|'
21 'nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|ru|rw|sa|'
22 'sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|sv|sy|sz|tc|td|tf|tg|th|'
23 'tj|tk|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|'
24 'vn|vu|wf|ws|ye|yt|yu|za|zm|zw'
25 ).split('|')
26
27
29 proto, netloc, path, params, query, frag = urlparse(url)
30 path = urlunparse(('', '', path, params, query, frag))
31
32 if ':' in netloc:
33 try:
34 host, port = netloc.split(':')
35 except ValueError:
36 return None
37 else:
38 host = netloc
39 port = ''
40
41 host = host.lower()
42 if not path:
43 path = '/'
44
45 return proto, host, port, path
46
48 """
49 This class represents an OpenID trust root. The C{L{parse}}
50 classmethod accepts a trust root string, producing a
51 C{L{TrustRoot}} object. The method OpenID server implementers
52 would be most likely to use is the C{L{isSane}} method, which
53 checks the trust root for given patterns that indicate that the
54 trust root is too broad or points to a local network resource.
55
56 @sort: parse, isSane
57 """
58
59 - def __init__(self, unparsed, proto, wildcard, host, port, path):
60 self.unparsed = unparsed
61 self.proto = proto
62 self.wildcard = wildcard
63 self.host = host
64 self.port = port
65 self.path = path
66
68 """
69 This method checks the to see if a trust root represents a
70 reasonable (sane) set of URLs. 'http://*.com/', for example
71 is not a reasonable pattern, as it cannot meaningfully specify
72 the site claiming it. This function attempts to find many
73 related examples, but it can only work via heuristics.
74 Negative responses from this method should be treated as
75 advisory, used only to alert the user to examine the trust
76 root carefully.
77
78
79 @return: Whether the trust root is sane
80
81 @rtype: C{bool}
82 """
83
84 if self.host == 'localhost':
85 return True
86
87 host_parts = self.host.split('.')
88 if self.wildcard:
89 assert host_parts[0] == '', host_parts
90 del host_parts[0]
91
92
93
94 if host_parts and not host_parts[-1]:
95 del host_parts[-1]
96
97 if not host_parts:
98 return False
99
100
101 if '' in host_parts:
102 return False
103
104 tld = host_parts[-1]
105 if tld not in _top_level_domains:
106 return False
107
108 if len(tld) == 2:
109 if len(host_parts) == 1:
110
111 return False
112
113 if len(host_parts[-2]) <= 3:
114
115
116
117 return len(host_parts) > 2
118 else:
119
120 return len(host_parts) > 1
121 else:
122
123 return len(host_parts) > 1
124
125
126 return False
127
129 """
130 Validates a URL against this trust root.
131
132
133 @param url: The URL to check
134
135 @type url: C{str}
136
137
138 @return: Whether the given URL is within this trust root.
139
140 @rtype: C{bool}
141 """
142
143 url_parts = _parseURL(url)
144 if url_parts is None:
145 return False
146
147 proto, host, port, path = url_parts
148
149 if proto != self.proto:
150 return False
151
152 if port != self.port:
153 return False
154
155 if '*' in host:
156 return False
157
158 if not self.wildcard:
159 if host != self.host:
160 return False
161 elif ((not host.endswith(self.host)) and
162 ('.' + host) != self.host):
163 return False
164
165 if path != self.path:
166 path_len = len(self.path)
167 trust_prefix = self.path[:path_len]
168 url_prefix = path[:path_len]
169
170
171 if trust_prefix != url_prefix:
172 return False
173
174
175
176
177 if '?' in self.path:
178 allowed = '&'
179 else:
180 allowed = '?/'
181
182 return (self.path[-1] in allowed or
183 path[path_len] in allowed)
184
185 return True
186
187 - def parse(cls, trust_root):
188 """
189 This method creates a C{L{TrustRoot}} instance from the given
190 input, if possible.
191
192
193 @param trust_root: This is the trust root to parse into a
194 C{L{TrustRoot}} object.
195
196 @type trust_root: C{str}
197
198
199 @return: A C{L{TrustRoot}} instance if trust_root parses as a
200 trust root, C{None} otherwise.
201
202 @rtype: C{NoneType} or C{L{TrustRoot}}
203 """
204 if not isinstance(trust_root, (str, unicode)):
205 return None
206
207 url_parts = _parseURL(trust_root)
208 if url_parts is None:
209 return None
210
211 proto, host, port, path = url_parts
212
213
214 if proto not in _protocols:
215 return None
216
217
218 if host.find('*', 1) != -1:
219
220 return None
221
222 if host.startswith('*'):
223
224
225 if len(host) > 1 and host[1] != '.':
226 return None
227
228 host = host[1:]
229 wilcard = True
230 else:
231 wilcard = False
232
233
234 tr = cls(trust_root, proto, wilcard, host, port, path)
235
236 return tr
237
238 parse = classmethod(parse)
239
241 """str -> bool
242
243 is this a sane trust root?
244 """
245 return cls.parse(trust_root_string).isSane()
246
247 checkSanity = classmethod(checkSanity)
248
250 """quick func for validating a url against a trust root. See the
251 TrustRoot class if you need more control."""
252 tr = cls.parse(trust_root)
253 return tr is not None and tr.validateURL(url)
254
255 checkURL = classmethod(checkURL)
256
258 return "TrustRoot('%s', '%s', '%s', '%s', '%s', '%s')" % (
259 self.unparsed, self.proto, self.wildcard, self.host, self.port,
260 self.path)
261
263 return repr(self)
264