Package openid :: Package server :: Module trustroot
[frames] | no frames]

Source Code for Module openid.server.trustroot

  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|eu|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   
28 -def _parseURL(url):
29 proto, netloc, path, params, query, frag = urlparse(url) 30 if not path: 31 # Python <2.4 does not parse URLs with no path properly 32 if not query and '?' in netloc: 33 netloc, query = netloc.split('?', 1) 34 35 path = '/' 36 37 path = urlunparse(('', '', path, params, query, frag)) 38 39 if ':' in netloc: 40 try: 41 host, port = netloc.split(':') 42 except ValueError: 43 return None 44 else: 45 host = netloc 46 port = '' 47 48 host = host.lower() 49 return proto, host, port, path
50
51 -class TrustRoot(object):
52 """ 53 This class represents an OpenID trust root. The C{L{parse}} 54 classmethod accepts a trust root string, producing a 55 C{L{TrustRoot}} object. The method OpenID server implementers 56 would be most likely to use is the C{L{isSane}} method, which 57 checks the trust root for given patterns that indicate that the 58 trust root is too broad or points to a local network resource. 59 60 @sort: parse, isSane 61 """ 62
63 - def __init__(self, unparsed, proto, wildcard, host, port, path):
64 self.unparsed = unparsed 65 self.proto = proto 66 self.wildcard = wildcard 67 self.host = host 68 self.port = port 69 self.path = path
70
71 - def isSane(self):
72 """ 73 This method checks the to see if a trust root represents a 74 reasonable (sane) set of URLs. 'http://*.com/', for example 75 is not a reasonable pattern, as it cannot meaningfully specify 76 the site claiming it. This function attempts to find many 77 related examples, but it can only work via heuristics. 78 Negative responses from this method should be treated as 79 advisory, used only to alert the user to examine the trust 80 root carefully. 81 82 83 @return: Whether the trust root is sane 84 85 @rtype: C{bool} 86 """ 87 88 if self.host == 'localhost': 89 return True 90 91 host_parts = self.host.split('.') 92 if self.wildcard: 93 assert host_parts[0] == '', host_parts 94 del host_parts[0] 95 96 # If it's an absolute domain name, remove the empty string 97 # from the end. 98 if host_parts and not host_parts[-1]: 99 del host_parts[-1] 100 101 if not host_parts: 102 return False 103 104 # Do not allow adjacent dots 105 if '' in host_parts: 106 return False 107 108 tld = host_parts[-1] 109 if tld not in _top_level_domains: 110 return False 111 112 if len(host_parts) == 1: 113 return False 114 115 if self.wildcard: 116 if len(tld) == 2 and len(host_parts[-2]) <= 3: 117 # It's a 2-letter tld with a short second to last segment 118 # so there needs to be more than two segments specified 119 # (e.g. *.co.uk is insane) 120 return len(host_parts) > 2 121 122 # Passed all tests for insanity. 123 return True
124
125 - def validateURL(self, url):
126 """ 127 Validates a URL against this trust root. 128 129 130 @param url: The URL to check 131 132 @type url: C{str} 133 134 135 @return: Whether the given URL is within this trust root. 136 137 @rtype: C{bool} 138 """ 139 140 url_parts = _parseURL(url) 141 if url_parts is None: 142 return False 143 144 proto, host, port, path = url_parts 145 146 if proto != self.proto: 147 return False 148 149 if port != self.port: 150 return False 151 152 if '*' in host: 153 return False 154 155 if not self.wildcard: 156 if host != self.host: 157 return False 158 elif ((not host.endswith(self.host)) and 159 ('.' + host) != self.host): 160 return False 161 162 if path != self.path: 163 path_len = len(self.path) 164 trust_prefix = self.path[:path_len] 165 url_prefix = path[:path_len] 166 167 # must be equal up to the length of the path, at least 168 if trust_prefix != url_prefix: 169 return False 170 171 # These characters must be on the boundary between the end 172 # of the trust root's path and the start of the URL's 173 # path. 174 if '?' in self.path: 175 allowed = '&' 176 else: 177 allowed = '?/' 178 179 return (self.path[-1] in allowed or 180 path[path_len] in allowed) 181 182 return True
183
184 - def parse(cls, trust_root):
185 """ 186 This method creates a C{L{TrustRoot}} instance from the given 187 input, if possible. 188 189 190 @param trust_root: This is the trust root to parse into a 191 C{L{TrustRoot}} object. 192 193 @type trust_root: C{str} 194 195 196 @return: A C{L{TrustRoot}} instance if trust_root parses as a 197 trust root, C{None} otherwise. 198 199 @rtype: C{NoneType} or C{L{TrustRoot}} 200 """ 201 if not isinstance(trust_root, (str, unicode)): 202 return None 203 204 url_parts = _parseURL(trust_root) 205 if url_parts is None: 206 return None 207 208 proto, host, port, path = url_parts 209 210 # check for valid prototype 211 if proto not in _protocols: 212 return None 213 214 # check for URI fragment 215 if path.find('#') != -1: 216 return None 217 218 # extract wildcard if it is there 219 if host.find('*', 1) != -1: 220 # wildcard must be at start of domain: *.foo.com, not foo.*.com 221 return None 222 223 if host.startswith('*'): 224 # Starts with star, so must have a dot after it (if a 225 # domain is specified) 226 if len(host) > 1 and host[1] != '.': 227 return None 228 229 host = host[1:] 230 wilcard = True 231 else: 232 wilcard = False 233 234 # we have a valid trust root 235 tr = cls(trust_root, proto, wilcard, host, port, path) 236 237 return tr
238 239 parse = classmethod(parse) 240
241 - def checkSanity(cls, trust_root_string):
242 """str -> bool 243 244 is this a sane trust root? 245 """ 246 return cls.parse(trust_root_string).isSane()
247 248 checkSanity = classmethod(checkSanity) 249
250 - def checkURL(cls, trust_root, url):
251 """quick func for validating a url against a trust root. See the 252 TrustRoot class if you need more control.""" 253 tr = cls.parse(trust_root) 254 return tr is not None and tr.validateURL(url)
255 256 checkURL = classmethod(checkURL) 257
258 - def __repr__(self):
259 return "TrustRoot('%s', '%s', '%s', '%s', '%s', '%s')" % ( 260 self.unparsed, self.proto, self.wildcard, self.host, self.port, 261 self.path)
262
263 - def __str__(self):
264 return repr(self)
265