1   
  2  """Functions to discover OpenID endpoints from identifiers. 
  3  """ 
  4   
  5  __all__ = [ 
  6      'DiscoveryFailure', 
  7      'OPENID_1_0_NS', 
  8      'OPENID_1_0_TYPE', 
  9      'OPENID_1_1_TYPE', 
 10      'OPENID_2_0_TYPE', 
 11      'OPENID_IDP_2_0_TYPE', 
 12      'OpenIDServiceEndpoint', 
 13      'discover', 
 14      ] 
 15   
 16  import urlparse 
 17   
 18  from openid import oidutil, fetchers, urinorm 
 19   
 20  from openid import yadis 
 21  from openid.yadis.etxrd import nsTag, XRDSError, XRD_NS_2_0 
 22  from openid.yadis.services import applyFilter as extractServices 
 23  from openid.yadis.discover import discover as yadisDiscover 
 24  from openid.yadis.discover import DiscoveryFailure 
 25  from openid.yadis import xrires, filters 
 26  from openid.yadis import xri 
 27   
 28  from openid.consumer import html_parse 
 29   
 30  OPENID_1_0_NS = 'http://openid.net/xmlns/1.0' 
 31  OPENID_IDP_2_0_TYPE = 'http://specs.openid.net/auth/2.0/server' 
 32  OPENID_2_0_TYPE = 'http://specs.openid.net/auth/2.0/signon' 
 33  OPENID_1_1_TYPE = 'http://openid.net/signon/1.1' 
 34  OPENID_1_0_TYPE = 'http://openid.net/signon/1.0' 
 35   
 36  from openid.message import OPENID1_NS as OPENID_1_0_MESSAGE_NS 
 37  from openid.message import OPENID2_NS as OPENID_2_0_MESSAGE_NS 
 38   
 40      """Object representing an OpenID service endpoint. 
 41   
 42      @ivar identity_url: the verified identifier. 
 43      @ivar canonicalID: For XRI, the persistent identifier. 
 44      """ 
 45   
 46       
 47       
 48      openid_type_uris = [ 
 49          OPENID_IDP_2_0_TYPE, 
 50   
 51          OPENID_2_0_TYPE, 
 52          OPENID_1_1_TYPE, 
 53          OPENID_1_0_TYPE, 
 54          ] 
 55   
 57          self.claimed_id = None 
 58          self.server_url = None 
 59          self.type_uris = [] 
 60          self.local_id = None 
 61          self.canonicalID = None 
 62          self.used_yadis = False  
 63          self.display_identifier = None 
  64   
 67   
 74   
 76          """Does this endpoint support this type? 
 77   
 78          I consider C{/server} endpoints to implicitly support C{/signon}. 
 79          """ 
 80          return ( 
 81              (type_uri in self.type_uris) or  
 82              (type_uri == OPENID_2_0_TYPE and self.isOPIdentifier()) 
 83              ) 
  84   
 86          """Return the display_identifier if set, else return the claimed_id. 
 87          """ 
 88          if self.display_identifier is not None: 
 89              return self.display_identifier 
 90          if self.claimed_id is None: 
 91              return None 
 92          else: 
 93              return urlparse.urldefrag(self.claimed_id)[0] 
  94   
 97   
100   
101 -    def parseService(self, yadis_url, uri, type_uris, service_element): 
 102          """Set the state of this object based on the contents of the 
103          service element.""" 
104          self.type_uris = type_uris 
105          self.server_url = uri 
106          self.used_yadis = True 
107   
108          if not self.isOPIdentifier(): 
109               
110               
111               
112               
113              self.local_id = findOPLocalIdentifier(service_element, 
114                                                    self.type_uris) 
115              self.claimed_id = yadis_url 
 116   
118          """Return the identifier that should be sent as the 
119          openid.identity parameter to the server.""" 
120           
121           
122           
123           
124          if (self.local_id is self.canonicalID is None): 
125              return self.claimed_id 
126          else: 
127              return self.local_id or self.canonicalID 
 128   
149   
150      fromBasicServiceEndpoint = classmethod(fromBasicServiceEndpoint) 
151   
153          """Parse the given document as HTML looking for an OpenID <link 
154          rel=...> 
155   
156          @rtype: [OpenIDServiceEndpoint] 
157          """ 
158          discovery_types = [ 
159              (OPENID_2_0_TYPE, 'openid2.provider', 'openid2.local_id'), 
160              (OPENID_1_1_TYPE, 'openid.server', 'openid.delegate'), 
161              ] 
162   
163          link_attrs = html_parse.parseLinkAttrs(html) 
164          services = [] 
165          for type_uri, op_endpoint_rel, local_id_rel in discovery_types: 
166              op_endpoint_url = html_parse.findFirstHref( 
167                  link_attrs, op_endpoint_rel) 
168              if op_endpoint_url is None: 
169                  continue 
170   
171              service = cls() 
172              service.claimed_id = uri 
173              service.local_id = html_parse.findFirstHref( 
174                  link_attrs, local_id_rel) 
175              service.server_url = op_endpoint_url 
176              service.type_uris = [type_uri] 
177   
178              services.append(service) 
179   
180          return services 
 181   
182      fromHTML = classmethod(fromHTML) 
183   
184   
186          """Parse the given document as XRDS looking for OpenID services. 
187   
188          @rtype: [OpenIDServiceEndpoint] 
189   
190          @raises XRDSError: When the XRDS does not parse. 
191   
192          @since: 2.1.0 
193          """ 
194          return extractServices(uri, xrds, cls) 
 195   
196      fromXRDS = classmethod(fromXRDS) 
197   
198   
200          """Create endpoints from a DiscoveryResult. 
201   
202          @type discoveryResult: L{DiscoveryResult} 
203   
204          @rtype: list of L{OpenIDServiceEndpoint} 
205   
206          @raises XRDSError: When the XRDS does not parse. 
207   
208          @since: 2.1.0 
209          """ 
210          if discoveryResult.isXRDS(): 
211              method = cls.fromXRDS 
212          else: 
213              method = cls.fromHTML 
214          return method(discoveryResult.normalized_uri, 
215                        discoveryResult.response_text) 
 216   
217      fromDiscoveryResult = classmethod(fromDiscoveryResult) 
218   
219   
221          """Construct an OP-Identifier OpenIDServiceEndpoint object for 
222          a given OP Endpoint URL 
223   
224          @param op_endpoint_url: The URL of the endpoint 
225          @rtype: OpenIDServiceEndpoint 
226          """ 
227          service = cls() 
228          service.server_url = op_endpoint_url 
229          service.type_uris = [OPENID_IDP_2_0_TYPE] 
230          return service 
 231   
232      fromOPEndpointURL = classmethod(fromOPEndpointURL) 
233   
234   
236          return ("<%s.%s " 
237                  "server_url=%r " 
238                  "claimed_id=%r " 
239                  "local_id=%r " 
240                  "canonicalID=%r " 
241                  "used_yadis=%s " 
242                  ">" 
243                   % (self.__class__.__module__, self.__class__.__name__, 
244                      self.server_url, 
245                      self.claimed_id, 
246                      self.local_id, 
247                      self.canonicalID, 
248                      self.used_yadis)) 
  249   
250   
251   
253      """Find the OP-Local Identifier for this xrd:Service element. 
254   
255      This considers openid:Delegate to be a synonym for xrd:LocalID if 
256      both OpenID 1.X and OpenID 2.0 types are present. If only OpenID 
257      1.X is present, it returns the value of openid:Delegate. If only 
258      OpenID 2.0 is present, it returns the value of xrd:LocalID. If 
259      there is more than one LocalID tag and the values are different, 
260      it raises a DiscoveryFailure. This is also triggered when the 
261      xrd:LocalID and openid:Delegate tags are different. 
262   
263      @param service_element: The xrd:Service element 
264      @type service_element: ElementTree.Node 
265   
266      @param type_uris: The xrd:Type values present in this service 
267          element. This function could extract them, but higher level 
268          code needs to do that anyway. 
269      @type type_uris: [str] 
270   
271      @raises DiscoveryFailure: when discovery fails. 
272   
273      @returns: The OP-Local Identifier for this service element, if one 
274          is present, or None otherwise. 
275      @rtype: str or unicode or NoneType 
276      """ 
277       
278   
279       
280      local_id_tags = [] 
281      if (OPENID_1_1_TYPE in type_uris or 
282          OPENID_1_0_TYPE in type_uris): 
283          local_id_tags.append(nsTag(OPENID_1_0_NS, 'Delegate')) 
284   
285      if OPENID_2_0_TYPE in type_uris: 
286          local_id_tags.append(nsTag(XRD_NS_2_0, 'LocalID')) 
287   
288       
289       
290      local_id = None 
291      for local_id_tag in local_id_tags: 
292          for local_id_element in service_element.findall(local_id_tag): 
293              if local_id is None: 
294                  local_id = local_id_element.text 
295              elif local_id != local_id_element.text: 
296                  format = 'More than one %r tag found in one service element' 
297                  message = format % (local_id_tag,) 
298                  raise DiscoveryFailure(message, None) 
299   
300      return local_id 
 301   
303      """Normalize a URL, converting normalization failures to 
304      DiscoveryFailure""" 
305      try: 
306          normalized = urinorm.urinorm(url) 
307      except ValueError, why: 
308          raise DiscoveryFailure('Normalizing identifier: %s' % (why[0],), None) 
309      else: 
310          return urlparse.urldefrag(normalized)[0] 
 311   
313      """Normalize an XRI, stripping its scheme if present""" 
314      if xri.startswith("xri://"): 
315          xri = xri[6:] 
316      return xri 
 317   
319      """Rearrange service_list in a new list so services are ordered by 
320      types listed in preferred_types.  Return the new list.""" 
321   
322      def enumerate(elts): 
323          """Return an iterable that pairs the index of an element with 
324          that element. 
325   
326          For Python 2.2 compatibility""" 
327          return zip(range(len(elts)), elts) 
 328   
329      def bestMatchingService(service): 
330          """Return the index of the first matching type, or something 
331          higher if no type matches. 
332   
333          This provides an ordering in which service elements that 
334          contain a type that comes earlier in the preferred types list 
335          come before service elements that come later. If a service 
336          element has more than one type, the most preferred one wins. 
337          """ 
338          for i, t in enumerate(preferred_types): 
339              if preferred_types[i] in service.type_uris: 
340                  return i 
341   
342          return len(preferred_types) 
343   
344       
345       
346      prio_services = [(bestMatchingService(s), orig_index, s) 
347                       for (orig_index, s) in enumerate(service_list)] 
348      prio_services.sort() 
349   
350       
351       
352      for i in range(len(prio_services)): 
353          prio_services[i] = prio_services[i][2] 
354   
355      return prio_services 
356   
358      """Extract OP Identifier services.  If none found, return the 
359      rest, sorted with most preferred first according to 
360      OpenIDServiceEndpoint.openid_type_uris. 
361   
362      openid_services is a list of OpenIDServiceEndpoint objects. 
363   
364      Returns a list of OpenIDServiceEndpoint objects.""" 
365   
366      op_services = arrangeByType(openid_services, [OPENID_IDP_2_0_TYPE]) 
367   
368      openid_services = arrangeByType(openid_services, 
369                                      OpenIDServiceEndpoint.openid_type_uris) 
370   
371      return op_services or openid_services 
 372   
374      """Discover OpenID services for a URI. Tries Yadis and falls back 
375      on old-style <link rel='...'> discovery if Yadis fails. 
376   
377      @param uri: normalized identity URL 
378      @type uri: str 
379   
380      @return: (claimed_id, services) 
381      @rtype: (str, list(OpenIDServiceEndpoint)) 
382   
383      @raises DiscoveryFailure: when discovery fails. 
384      """ 
385       
386       
387       
388       
389      response = yadisDiscover(uri) 
390   
391      yadis_url = response.normalized_uri 
392      body = response.response_text 
393      try: 
394          openid_services = OpenIDServiceEndpoint.fromXRDS(yadis_url, body) 
395      except XRDSError: 
396           
397          openid_services = [] 
398   
399      if not openid_services: 
400           
401   
402          if response.isXRDS(): 
403               
404               
405               
406              return discoverNoYadis(uri) 
407   
408           
409           
410          openid_services = OpenIDServiceEndpoint.fromHTML(yadis_url, body) 
411   
412      return (yadis_url, getOPOrUserServices(openid_services)) 
 413   
439   
440   
452   
454      parsed = urlparse.urlparse(uri) 
455      if parsed[0] and parsed[1]: 
456          if parsed[0] not in ['http', 'https']: 
457              raise DiscoveryFailure('URI scheme is not HTTP or HTTPS', None) 
458      else: 
459          uri = 'http://' + uri 
460   
461      uri = normalizeURL(uri) 
462      claimed_id, openid_services = discoverYadis(uri) 
463      claimed_id = normalizeURL(claimed_id) 
464      return claimed_id, openid_services 
 465   
471