1   
   2  """OpenID support for Relying Parties (aka Consumers). 
   3   
   4  This module documents the main interface with the OpenID consumer 
   5  library.  The only part of the library which has to be used and isn't 
   6  documented in full here is the store required to create an 
   7  C{L{Consumer}} instance.  More on the abstract store type and 
   8  concrete implementations of it that are provided in the documentation 
   9  for the C{L{__init__<Consumer.__init__>}} method of the 
  10  C{L{Consumer}} class. 
  11   
  12   
  13  OVERVIEW 
  14  ======== 
  15   
  16      The OpenID identity verification process most commonly uses the 
  17      following steps, as visible to the user of this library: 
  18   
  19          1. The user enters their OpenID into a field on the consumer's 
  20             site, and hits a login button. 
  21   
  22          2. The consumer site discovers the user's OpenID provider using 
  23             the Yadis protocol. 
  24   
  25          3. The consumer site sends the browser a redirect to the 
  26             OpenID provider.  This is the authentication request as 
  27             described in the OpenID specification. 
  28   
  29          4. The OpenID provider's site sends the browser a redirect 
  30             back to the consumer site.  This redirect contains the 
  31             provider's response to the authentication request. 
  32   
  33      The most important part of the flow to note is the consumer's site 
  34      must handle two separate HTTP requests in order to perform the 
  35      full identity check. 
  36   
  37   
  38  LIBRARY DESIGN 
  39  ============== 
  40   
  41      This consumer library is designed with that flow in mind.  The 
  42      goal is to make it as easy as possible to perform the above steps 
  43      securely. 
  44   
  45      At a high level, there are two important parts in the consumer 
  46      library.  The first important part is this module, which contains 
  47      the interface to actually use this library.  The second is the 
  48      C{L{openid.store.interface}} module, which describes the 
  49      interface to use if you need to create a custom method for storing 
  50      the state this library needs to maintain between requests. 
  51   
  52      In general, the second part is less important for users of the 
  53      library to know about, as several implementations are provided 
  54      which cover a wide variety of situations in which consumers may 
  55      use the library. 
  56   
  57      This module contains a class, C{L{Consumer}}, with methods 
  58      corresponding to the actions necessary in each of steps 2, 3, and 
  59      4 described in the overview.  Use of this library should be as easy 
  60      as creating an C{L{Consumer}} instance and calling the methods 
  61      appropriate for the action the site wants to take. 
  62   
  63   
  64  SESSIONS, STORES, AND STATELESS MODE 
  65  ==================================== 
  66   
  67      The C{L{Consumer}} object keeps track of two types of state: 
  68   
  69          1. State of the user's current authentication attempt.  Things like 
  70             the identity URL, the list of endpoints discovered for that 
  71             URL, and in case where some endpoints are unreachable, the list 
  72             of endpoints already tried.  This state needs to be held from 
  73             Consumer.begin() to Consumer.complete(), but it is only applicable 
  74             to a single session with a single user agent, and at the end of 
  75             the authentication process (i.e. when an OP replies with either 
  76             C{id_res} or C{cancel}) it may be discarded. 
  77   
  78          2. State of relationships with servers, i.e. shared secrets 
  79             (associations) with servers and nonces seen on signed messages. 
  80             This information should persist from one session to the next and 
  81             should not be bound to a particular user-agent. 
  82   
  83   
  84      These two types of storage are reflected in the first two arguments of 
  85      Consumer's constructor, C{session} and C{store}.  C{session} is a 
  86      dict-like object and we hope your web framework provides you with one 
  87      of these bound to the user agent.  C{store} is an instance of 
  88      L{openid.store.interface.OpenIDStore}. 
  89   
  90      Since the store does hold secrets shared between your application and the 
  91      OpenID provider, you should be careful about how you use it in a shared 
  92      hosting environment.  If the filesystem or database permissions of your 
  93      web host allow strangers to read from them, do not store your data there! 
  94      If you have no safe place to store your data, construct your consumer 
  95      with C{None} for the store, and it will operate only in stateless mode. 
  96      Stateless mode may be slower, put more load on the OpenID provider, and 
  97      trusts the provider to keep you safe from replay attacks. 
  98   
  99   
 100      Several store implementation are provided, and the interface is 
 101      fully documented so that custom stores can be used as well.  See 
 102      the documentation for the C{L{Consumer}} class for more 
 103      information on the interface for stores.  The implementations that 
 104      are provided allow the consumer site to store the necessary data 
 105      in several different ways, including several SQL databases and 
 106      normal files on disk. 
 107   
 108   
 109  IMMEDIATE MODE 
 110  ============== 
 111   
 112      In the flow described above, the user may need to confirm to the 
 113      OpenID provider that it's ok to disclose his or her identity. 
 114      The provider may draw pages asking for information from the user 
 115      before it redirects the browser back to the consumer's site.  This 
 116      is generally transparent to the consumer site, so it is typically 
 117      ignored as an implementation detail. 
 118   
 119      There can be times, however, where the consumer site wants to get 
 120      a response immediately.  When this is the case, the consumer can 
 121      put the library in immediate mode.  In immediate mode, there is an 
 122      extra response possible from the server, which is essentially the 
 123      server reporting that it doesn't have enough information to answer 
 124      the question yet. 
 125   
 126   
 127  USING THIS LIBRARY 
 128  ================== 
 129   
 130      Integrating this library into an application is usually a 
 131      relatively straightforward process.  The process should basically 
 132      follow this plan: 
 133   
 134      Add an OpenID login field somewhere on your site.  When an OpenID 
 135      is entered in that field and the form is submitted, it should make 
 136      a request to your site which includes that OpenID URL. 
 137   
 138      First, the application should L{instantiate a Consumer<Consumer.__init__>} 
 139      with a session for per-user state and store for shared state. 
 140      using the store of choice. 
 141   
 142      Next, the application should call the 'C{L{begin<Consumer.begin>}}' method on the 
 143      C{L{Consumer}} instance.  This method takes the OpenID URL.  The 
 144      C{L{begin<Consumer.begin>}} method returns an C{L{AuthRequest}} 
 145      object. 
 146   
 147      Next, the application should call the 
 148      C{L{redirectURL<AuthRequest.redirectURL>}} method on the 
 149      C{L{AuthRequest}} object.  The parameter C{return_to} is the URL 
 150      that the OpenID server will send the user back to after attempting 
 151      to verify his or her identity.  The C{realm} parameter is the 
 152      URL (or URL pattern) that identifies your web site to the user 
 153      when he or she is authorizing it.  Send a redirect to the 
 154      resulting URL to the user's browser. 
 155   
 156      That's the first half of the authentication process.  The second 
 157      half of the process is done after the user's OpenID Provider sends the 
 158      user's browser a redirect back to your site to complete their 
 159      login. 
 160   
 161      When that happens, the user will contact your site at the URL 
 162      given as the C{return_to} URL to the 
 163      C{L{redirectURL<AuthRequest.redirectURL>}} call made 
 164      above.  The request will have several query parameters added to 
 165      the URL by the OpenID provider as the information necessary to 
 166      finish the request. 
 167   
 168      Get a C{L{Consumer}} instance with the same session and store as 
 169      before and call its C{L{complete<Consumer.complete>}} method, 
 170      passing in all the received query arguments. 
 171   
 172      There are multiple possible return types possible from that 
 173      method. These indicate whether or not the login was successful, 
 174      and include any additional information appropriate for their type. 
 175   
 176  @var SUCCESS: constant used as the status for 
 177      L{SuccessResponse<openid.consumer.consumer.SuccessResponse>} objects. 
 178   
 179  @var FAILURE: constant used as the status for 
 180      L{FailureResponse<openid.consumer.consumer.FailureResponse>} objects. 
 181   
 182  @var CANCEL: constant used as the status for 
 183      L{CancelResponse<openid.consumer.consumer.CancelResponse>} objects. 
 184   
 185  @var SETUP_NEEDED: constant used as the status for 
 186      L{SetupNeededResponse<openid.consumer.consumer.SetupNeededResponse>} 
 187      objects. 
 188  """ 
 189   
 190  import cgi 
 191  import copy 
 192  from urlparse import urlparse, urldefrag 
 193   
 194  from openid import fetchers 
 195   
 196  from openid.consumer.discover import discover, OpenIDServiceEndpoint, \ 
 197       DiscoveryFailure, OPENID_1_0_TYPE, OPENID_1_1_TYPE, OPENID_2_0_TYPE 
 198  from openid.message import Message, OPENID_NS, OPENID2_NS, OPENID1_NS, \ 
 199       IDENTIFIER_SELECT, no_default, BARE_NS 
 200  from openid import cryptutil 
 201  from openid import oidutil 
 202  from openid.association import Association, default_negotiator, \ 
 203       SessionNegotiator 
 204  from openid.dh import DiffieHellman 
 205  from openid.store.nonce import mkNonce, split as splitNonce 
 206  from openid.yadis.manager import Discovery 
 207  from openid import urinorm 
 208   
 209   
 210  __all__ = ['AuthRequest', 'Consumer', 'SuccessResponse', 
 211             'SetupNeededResponse', 'CancelResponse', 'FailureResponse', 
 212             'SUCCESS', 'FAILURE', 'CANCEL', 'SETUP_NEEDED', 
 213             ] 
 214   
 215   
 216 -def makeKVPost(request_message, server_url): 
  217      """Make a Direct Request to an OpenID Provider and return the 
 218      result as a Message object. 
 219   
 220      @raises openid.fetchers.HTTPFetchingError: if an error is 
 221          encountered in making the HTTP post. 
 222   
 223      @rtype: L{openid.message.Message} 
 224      """ 
 225       
 226      resp = fetchers.fetch(server_url, body=request_message.toURLEncoded()) 
 227   
 228       
 229      return _httpResponseToMessage(resp, server_url) 
  230   
 231   
 233      """Adapt a POST response to a Message. 
 234   
 235      @type response: L{openid.fetchers.HTTPResponse} 
 236      @param response: Result of a POST to an OpenID endpoint. 
 237   
 238      @rtype: L{openid.message.Message} 
 239   
 240      @raises openid.fetchers.HTTPFetchingError: if the server returned a 
 241          status of other than 200 or 400. 
 242   
 243      @raises ServerError: if the server returned an OpenID error. 
 244      """ 
 245       
 246      response_message = Message.fromKVForm(response.body) 
 247      if response.status == 400: 
 248          raise ServerError.fromMessage(response_message) 
 249   
 250      elif response.status not in (200, 206): 
 251          fmt = 'bad status code from server %s: %s' 
 252          error_message = fmt % (server_url, response.status) 
 253          raise fetchers.HTTPFetchingError(error_message) 
 254   
 255      return response_message 
  256   
 257   
 258   
 260      """An OpenID consumer implementation that performs discovery and 
 261      does session management. 
 262   
 263      @ivar consumer: an instance of an object implementing the OpenID 
 264          protocol, but doing no discovery or session management. 
 265   
 266      @type consumer: GenericConsumer 
 267   
 268      @ivar session: A dictionary-like object representing the user's 
 269          session data.  This is used for keeping state of the OpenID 
 270          transaction when the user is redirected to the server. 
 271   
 272      @cvar session_key_prefix: A string that is prepended to session 
 273          keys to ensure that they are unique. This variable may be 
 274          changed to suit your application. 
 275      """ 
 276      session_key_prefix = "_openid_consumer_" 
 277   
 278      _token = 'last_token' 
 279   
 280      _discover = staticmethod(discover) 
 281   
 282 -    def __init__(self, session, store, consumer_class=None): 
  283          """Initialize a Consumer instance. 
 284   
 285          You should create a new instance of the Consumer object with 
 286          every HTTP request that handles OpenID transactions. 
 287   
 288          @param session: See L{the session instance variable<openid.consumer.consumer.Consumer.session>} 
 289   
 290          @param store: an object that implements the interface in 
 291              C{L{openid.store.interface.OpenIDStore}}.  Several 
 292              implementations are provided, to cover common database 
 293              environments. 
 294   
 295          @type store: C{L{openid.store.interface.OpenIDStore}} 
 296   
 297          @see: L{openid.store.interface} 
 298          @see: L{openid.store} 
 299          """ 
 300          self.session = session 
 301          if consumer_class is None: 
 302              consumer_class = GenericConsumer 
 303          self.consumer = consumer_class(store) 
 304          self._token_key = self.session_key_prefix + self._token 
  305   
 306 -    def begin(self, user_url, anonymous=False): 
  307          """Start the OpenID authentication process. See steps 1-2 in 
 308          the overview at the top of this file. 
 309   
 310          @param user_url: Identity URL given by the user. This method 
 311              performs a textual transformation of the URL to try and 
 312              make sure it is normalized. For example, a user_url of 
 313              example.com will be normalized to http://example.com/ 
 314              normalizing and resolving any redirects the server might 
 315              issue. 
 316   
 317          @type user_url: unicode 
 318   
 319          @param anonymous: Whether to make an anonymous request of the OpenID 
 320              provider.  Such a request does not ask for an authorization 
 321              assertion for an OpenID identifier, but may be used with 
 322              extensions to pass other data.  e.g. "I don't care who you are, 
 323              but I'd like to know your time zone." 
 324   
 325          @type anonymous: bool 
 326   
 327          @returns: An object containing the discovered information will 
 328              be returned, with a method for building a redirect URL to 
 329              the server, as described in step 3 of the overview. This 
 330              object may also be used to add extension arguments to the 
 331              request, using its 
 332              L{addExtensionArg<openid.consumer.consumer.AuthRequest.addExtensionArg>} 
 333              method. 
 334   
 335          @returntype: L{AuthRequest<openid.consumer.consumer.AuthRequest>} 
 336   
 337          @raises openid.consumer.discover.DiscoveryFailure: when I fail to 
 338              find an OpenID server for this URL.  If the C{yadis} package 
 339              is available, L{openid.consumer.discover.DiscoveryFailure} is 
 340              an alias for C{yadis.discover.DiscoveryFailure}. 
 341          """ 
 342          disco = Discovery(self.session, user_url, self.session_key_prefix) 
 343          try: 
 344              service = disco.getNextService(self._discover) 
 345          except fetchers.HTTPFetchingError, why: 
 346              raise DiscoveryFailure( 
 347                  'Error fetching XRDS document: %s' % (why[0],), None) 
 348   
 349          if service is None: 
 350              raise DiscoveryFailure( 
 351                  'No usable OpenID services found for %s' % (user_url,), None) 
 352          else: 
 353              return self.beginWithoutDiscovery(service, anonymous) 
  354   
 356          """Start OpenID verification without doing OpenID server 
 357          discovery. This method is used internally by Consumer.begin 
 358          after discovery is performed, and exists to provide an 
 359          interface for library users needing to perform their own 
 360          discovery. 
 361   
 362          @param service: an OpenID service endpoint descriptor.  This 
 363              object and factories for it are found in the 
 364              L{openid.consumer.discover} module. 
 365   
 366          @type service: 
 367              L{OpenIDServiceEndpoint<openid.consumer.discover.OpenIDServiceEndpoint>} 
 368   
 369          @returns: an OpenID authentication request object. 
 370   
 371          @rtype: L{AuthRequest<openid.consumer.consumer.AuthRequest>} 
 372   
 373          @See: Openid.consumer.consumer.Consumer.begin 
 374          @see: openid.consumer.discover 
 375          """ 
 376          auth_req = self.consumer.begin(service) 
 377          self.session[self._token_key] = auth_req.endpoint 
 378   
 379          try: 
 380              auth_req.setAnonymous(anonymous) 
 381          except ValueError, why: 
 382              raise ProtocolError(str(why)) 
 383   
 384          return auth_req 
  385   
 386 -    def complete(self, query, current_url): 
  387          """Called to interpret the server's response to an OpenID 
 388          request. It is called in step 4 of the flow described in the 
 389          consumer overview. 
 390   
 391          @param query: A dictionary of the query parameters for this 
 392              HTTP request. 
 393   
 394          @param current_url: The URL used to invoke the application. 
 395              Extract the URL from your application's web 
 396              request framework and specify it here to have it checked 
 397              against the openid.return_to value in the response.  If 
 398              the return_to URL check fails, the status of the 
 399              completion will be FAILURE. 
 400   
 401          @returns: a subclass of Response. The type of response is 
 402              indicated by the status attribute, which will be one of 
 403              SUCCESS, CANCEL, FAILURE, or SETUP_NEEDED. 
 404   
 405          @see: L{SuccessResponse<openid.consumer.consumer.SuccessResponse>} 
 406          @see: L{CancelResponse<openid.consumer.consumer.CancelResponse>} 
 407          @see: L{SetupNeededResponse<openid.consumer.consumer.SetupNeededResponse>} 
 408          @see: L{FailureResponse<openid.consumer.consumer.FailureResponse>} 
 409          """ 
 410   
 411          endpoint = self.session.get(self._token_key) 
 412   
 413          message = Message.fromPostArgs(query) 
 414          response = self.consumer.complete(message, endpoint, current_url) 
 415   
 416          try: 
 417              del self.session[self._token_key] 
 418          except KeyError: 
 419              pass 
 420   
 421          if (response.status in ['success', 'cancel'] and 
 422              response.identity_url is not None): 
 423   
 424              disco = Discovery(self.session, 
 425                                response.identity_url, 
 426                                self.session_key_prefix) 
 427               
 428               
 429              disco.cleanup(force=True) 
 430   
 431          return response 
  432   
 434          """Set the order in which association types/sessions should be 
 435          attempted. For instance, to only allow HMAC-SHA256 
 436          associations created with a DH-SHA256 association session: 
 437   
 438          >>> consumer.setAssociationPreference([('HMAC-SHA256', 'DH-SHA256')]) 
 439   
 440          Any association type/association type pair that is not in this 
 441          list will not be attempted at all. 
 442   
 443          @param association_preferences: The list of allowed 
 444              (association type, association session type) pairs that 
 445              should be allowed for this consumer to use, in order from 
 446              most preferred to least preferred. 
 447          @type association_preferences: [(str, str)] 
 448   
 449          @returns: None 
 450   
 451          @see: C{L{openid.association.SessionNegotiator}} 
 452          """ 
 453          self.consumer.negotiator = SessionNegotiator(association_preferences) 
   454   
 487   
 493   
 495      session_type = 'no-encryption' 
 496      allowed_assoc_types = ['HMAC-SHA1', 'HMAC-SHA256'] 
 497   
 498 -    def getRequest(self): 
  500   
 502          mac_key64 = response.getArg(OPENID_NS, 'mac_key', no_default) 
 503          return oidutil.fromBase64(mac_key64) 
   504   
 506      """Internally-used exception that indicates that an immediate-mode 
 507      request cancelled.""" 
 508 -    def __init__(self, user_setup_url=None): 
  509          Exception.__init__(self, user_setup_url) 
 510          self.user_setup_url = user_setup_url 
   511   
 513      """Exception that indicates that a message violated the 
 514      protocol. It is raised and caught internally to this file.""" 
  515   
 517      """A protocol error arising from type URIs mismatching 
 518      """ 
 519   
 520 -    def __init__(self, expected, endpoint): 
  524   
 526          s = '<%s.%s: Required type %s not found in %s for endpoint %s>' % ( 
 527              self.__class__.__module__, self.__class__.__name__, 
 528              self.expected, self.endpoint.type_uris, self.endpoint) 
 529          return s 
   530   
 531   
 532   
 534      """Exception that is raised when the server returns a 400 response 
 535      code to a direct request.""" 
 536   
 537 -    def __init__(self, error_text, error_code, message): 
  538          Exception.__init__(self, error_text) 
 539          self.error_text = error_text 
 540          self.error_code = error_code 
 541          self.message = message 
  542   
 544          """Generate a ServerError instance, extracting the error text 
 545          and the error code from the message.""" 
 546          error_text = message.getArg( 
 547              OPENID_NS, 'error', '<no error message supplied>') 
 548          error_code = message.getArg(OPENID_NS, 'error_code') 
 549          return cls(error_text, error_code, message) 
  550   
 551      fromMessage = classmethod(fromMessage) 
  552   
 554      """This is the implementation of the common logic for OpenID 
 555      consumers. It is unaware of the application in which it is 
 556      running. 
 557   
 558      @ivar negotiator: An object that controls the kind of associations 
 559          that the consumer makes. It defaults to 
 560          C{L{openid.association.default_negotiator}}. Assign a 
 561          different negotiator to it if you have specific requirements 
 562          for how associations are made. 
 563      @type negotiator: C{L{openid.association.SessionNegotiator}} 
 564      """ 
 565   
 566       
 567       
 568       
 569       
 570       
 571       
 572      openid1_nonce_query_arg_name = 'janrain_nonce' 
 573   
 574       
 575       
 576       
 577      openid1_return_to_identifier_name = 'openid1_claimed_id' 
 578   
 579      session_types = { 
 580          'DH-SHA1':DiffieHellmanSHA1ConsumerSession, 
 581          'DH-SHA256':DiffieHellmanSHA256ConsumerSession, 
 582          'no-encryption':PlainTextConsumerSession, 
 583          } 
 584   
 585      _discover = staticmethod(discover) 
 586   
 590   
 591 -    def begin(self, service_endpoint): 
  608   
 609 -    def complete(self, message, endpoint, return_to): 
  610          """Process the OpenID message, using the specified endpoint 
 611          and return_to URL as context. This method will handle any 
 612          OpenID message that is sent to the return_to URL. 
 613          """ 
 614          mode = message.getArg(OPENID_NS, 'mode', '<No mode set>') 
 615   
 616          modeMethod = getattr(self, '_complete_' + mode, 
 617                               self._completeInvalid) 
 618   
 619          return modeMethod(message, endpoint, return_to) 
  620   
 623   
 631   
 638   
 649   
 654   
 656          """Check an OpenID message and its openid.return_to value 
 657          against a return_to URL from an application.  Return True on 
 658          success, False on failure. 
 659          """ 
 660           
 661           
 662          try: 
 663              self._verifyReturnToArgs(message.toPostArgs()) 
 664          except ProtocolError, why: 
 665              oidutil.log("Verifying return_to arguments: %s" % (why[0],)) 
 666              return False 
 667   
 668           
 669          msg_return_to = message.getArg(OPENID_NS, 'return_to') 
 670   
 671           
 672           
 673          app_parts = urlparse(urinorm.urinorm(return_to)) 
 674          msg_parts = urlparse(urinorm.urinorm(msg_return_to)) 
 675   
 676           
 677           
 678          for part in range(0, 3): 
 679              if app_parts[part] != msg_parts[part]: 
 680                  return False 
 681   
 682          return True 
  683   
 684      _makeKVPost = staticmethod(makeKVPost) 
 685   
 687          """Check an id_res message to see if it is a 
 688          checkid_immediate cancel response. 
 689   
 690          @raises SetupNeededError: if it is a checkid_immediate cancellation 
 691          """ 
 692           
 693           
 694           
 695          if message.isOpenID1(): 
 696              user_setup_url = message.getArg(OPENID1_NS, 'user_setup_url') 
 697              if user_setup_url is not None: 
 698                  raise SetupNeededError(user_setup_url) 
  699   
 700 -    def _doIdRes(self, message, endpoint, return_to): 
  701          """Handle id_res responses that are not cancellations of 
 702          immediate mode requests. 
 703   
 704          @param message: the response paramaters. 
 705          @param endpoint: the discovered endpoint object. May be None. 
 706   
 707          @raises ProtocolError: If the message contents are not 
 708              well-formed according to the OpenID specification. This 
 709              includes missing fields or not signing fields that should 
 710              be signed. 
 711   
 712          @raises DiscoveryFailure: If the subject of the id_res message 
 713              does not match the supplied endpoint, and discovery on the 
 714              identifier in the message fails (this should only happen 
 715              when using OpenID 2) 
 716   
 717          @returntype: L{Response} 
 718          """ 
 719           
 720           
 721          self._idResCheckForFields(message) 
 722   
 723          if not self._checkReturnTo(message, return_to): 
 724              raise ProtocolError( 
 725                  "return_to does not match return URL. Expected %r, got %r" 
 726                  % (return_to, message.getArg(OPENID_NS, 'return_to'))) 
 727   
 728   
 729           
 730          endpoint = self._verifyDiscoveryResults(message, endpoint) 
 731          oidutil.log("Received id_res response from %s using association %s" % 
 732                      (endpoint.server_url, 
 733                       message.getArg(OPENID_NS, 'assoc_handle'))) 
 734   
 735          self._idResCheckSignature(message, endpoint.server_url) 
 736   
 737           
 738          self._idResCheckNonce(message, endpoint) 
 739   
 740          signed_list_str = message.getArg(OPENID_NS, 'signed', no_default) 
 741          signed_list = signed_list_str.split(',') 
 742          signed_fields = ["openid." + s for s in signed_list] 
 743          return SuccessResponse(endpoint, message, signed_fields) 
  744   
 746          """Extract the nonce from an OpenID 1 response.  Return the 
 747          nonce from the BARE_NS since we independently check the 
 748          return_to arguments are the same as those in the response 
 749          message. 
 750   
 751          See the openid1_nonce_query_arg_name class variable 
 752   
 753          @returns: The nonce as a string or None 
 754          """ 
 755          return message.getArg(BARE_NS, self.openid1_nonce_query_arg_name) 
  756   
 777   
 805   
 807           
 808           
 809           
 810           
 811           
 812           
 813          basic_fields = ['return_to', 'assoc_handle', 'sig', 'signed'] 
 814          basic_sig_fields = ['return_to', 'identity'] 
 815   
 816          require_fields = { 
 817              OPENID2_NS: basic_fields + ['op_endpoint'], 
 818              OPENID1_NS: basic_fields + ['identity'], 
 819              } 
 820   
 821          require_sigs = { 
 822              OPENID2_NS: basic_sig_fields + ['response_nonce', 
 823                                              'claimed_id', 
 824                                              'assoc_handle', 
 825                                              'op_endpoint',], 
 826              OPENID1_NS: basic_sig_fields, 
 827              } 
 828   
 829          for field in require_fields[message.getOpenIDNamespace()]: 
 830              if not message.hasKey(OPENID_NS, field): 
 831                  raise ProtocolError('Missing required field %r' % (field,)) 
 832   
 833          signed_list_str = message.getArg(OPENID_NS, 'signed', no_default) 
 834          signed_list = signed_list_str.split(',') 
 835   
 836          for field in require_sigs[message.getOpenIDNamespace()]: 
 837               
 838              if message.hasKey(OPENID_NS, field) and field not in signed_list: 
 839                  raise ProtocolError('"%s" not signed' % (field,)) 
  840   
 841   
 843          """Verify that the arguments in the return_to URL are present in this 
 844          response. 
 845          """ 
 846          message = Message.fromPostArgs(query) 
 847          return_to = message.getArg(OPENID_NS, 'return_to') 
 848   
 849          if return_to is None: 
 850              raise ProtocolError('Response has no return_to') 
 851   
 852          parsed_url = urlparse(return_to) 
 853          rt_query = parsed_url[4] 
 854          parsed_args = cgi.parse_qsl(rt_query) 
 855   
 856          for rt_key, rt_value in parsed_args: 
 857              try: 
 858                  value = query[rt_key] 
 859                  if rt_value != value: 
 860                      format = ("parameter %s value %r does not match " 
 861                                "return_to's value %r") 
 862                      raise ProtocolError(format % (rt_key, value, rt_value)) 
 863              except KeyError: 
 864                  format = "return_to parameter %s absent from query %r" 
 865                  raise ProtocolError(format % (rt_key, query)) 
 866   
 867           
 868           
 869          bare_args = message.getArgs(BARE_NS) 
 870          for pair in bare_args.iteritems(): 
 871              if pair not in parsed_args: 
 872                  raise ProtocolError("Parameter %s not in return_to URL" % (pair[0],)) 
  873   
 874      _verifyReturnToArgs = staticmethod(_verifyReturnToArgs) 
 875   
 877          """ 
 878          Extract the information from an OpenID assertion message and 
 879          verify it against the original 
 880   
 881          @param endpoint: The endpoint that resulted from doing discovery 
 882          @param resp_msg: The id_res message object 
 883   
 884          @returns: the verified endpoint 
 885          """ 
 886          if resp_msg.getOpenIDNamespace() == OPENID2_NS: 
 887              return self._verifyDiscoveryResultsOpenID2(resp_msg, endpoint) 
 888          else: 
 889              return self._verifyDiscoveryResultsOpenID1(resp_msg, endpoint) 
  890   
 891   
 947   
 949          claimed_id = resp_msg.getArg(BARE_NS, self.openid1_return_to_identifier_name) 
 950   
 951          if endpoint is None and claimed_id is None: 
 952              raise RuntimeError( 
 953                  'When using OpenID 1, the claimed ID must be supplied, ' 
 954                  'either by passing it through as a return_to parameter ' 
 955                  'or by using a session, and supplied to the GenericConsumer ' 
 956                  'as the argument to complete()') 
 957          elif endpoint is not None and claimed_id is None: 
 958              claimed_id = endpoint.claimed_id 
 959   
 960          to_match = OpenIDServiceEndpoint() 
 961          to_match.type_uris = [OPENID_1_1_TYPE] 
 962          to_match.local_id = resp_msg.getArg(OPENID1_NS, 'identity') 
 963           
 964          to_match.claimed_id = claimed_id 
 965   
 966          if to_match.local_id is None: 
 967              raise ProtocolError('Missing required field openid.identity') 
 968   
 969          to_match_1_0 = copy.copy(to_match) 
 970          to_match_1_0.type_uris = [OPENID_1_0_TYPE] 
 971   
 972          if endpoint is not None: 
 973              try: 
 974                  try: 
 975                      self._verifyDiscoverySingle(endpoint, to_match) 
 976                  except TypeURIMismatch: 
 977                      self._verifyDiscoverySingle(endpoint, to_match_1_0) 
 978              except ProtocolError, e: 
 979                  oidutil.log("Error attempting to use stored discovery information: " + 
 980                              str(e)) 
 981                  oidutil.log("Attempting discovery to verify endpoint") 
 982              else: 
 983                  return endpoint 
 984   
 985           
 986          return self._discoverAndVerify(claimed_id, [to_match, to_match_1_0]) 
  987   
 989          """Verify that the given endpoint matches the information 
 990          extracted from the OpenID assertion, and raise an exception if 
 991          there is a mismatch. 
 992   
 993          @type endpoint: openid.consumer.discover.OpenIDServiceEndpoint 
 994          @type to_match: openid.consumer.discover.OpenIDServiceEndpoint 
 995   
 996          @rtype: NoneType 
 997   
 998          @raises ProtocolError: when the endpoint does not match the 
 999              discovered information. 
1000          """ 
1001           
1002           
1003          for type_uri in to_match.type_uris: 
1004              if not endpoint.usesExtension(type_uri): 
1005                  raise TypeURIMismatch(type_uri, endpoint) 
1006   
1007           
1008           
1009          defragged_claimed_id, _ = urldefrag(to_match.claimed_id) 
1010          if defragged_claimed_id != endpoint.claimed_id: 
1011              raise ProtocolError( 
1012                  'Claimed ID does not match (different subjects!), ' 
1013                  'Expected %s, got %s' % 
1014                  (defragged_claimed_id, endpoint.claimed_id)) 
1015   
1016          if to_match.getLocalID() != endpoint.getLocalID(): 
1017              raise ProtocolError('local_id mismatch. Expected %s, got %s' % 
1018                                  (to_match.getLocalID(), endpoint.getLocalID())) 
1019   
1020           
1021           
1022           
1023           
1024           
1025          if to_match.server_url is None: 
1026              assert to_match.preferredNamespace() == OPENID1_NS, ( 
1027                  """The code calling this must ensure that OpenID 2 
1028                  responses have a non-none `openid.op_endpoint' and 
1029                  that it is set as the `server_url' attribute of the 
1030                  `to_match' endpoint.""") 
1031   
1032          elif to_match.server_url != endpoint.server_url: 
1033              raise ProtocolError('OP Endpoint mismatch. Expected %s, got %s' % 
1034                                  (to_match.server_url, endpoint.server_url)) 
 1035   
1037          """Given an endpoint object created from the information in an 
1038          OpenID response, perform discovery and verify the discovery 
1039          results, returning the matching endpoint that is the result of 
1040          doing that discovery. 
1041   
1042          @type to_match: openid.consumer.discover.OpenIDServiceEndpoint 
1043          @param to_match: The endpoint whose information we're confirming 
1044   
1045          @rtype: openid.consumer.discover.OpenIDServiceEndpoint 
1046          @returns: The result of performing discovery on the claimed 
1047              identifier in `to_match' 
1048   
1049          @raises DiscoveryFailure: when discovery fails. 
1050          """ 
1051          oidutil.log('Performing discovery on %s' % (claimed_id,)) 
1052          _, services = self._discover(claimed_id) 
1053          if not services: 
1054              raise DiscoveryFailure('No OpenID information found at %s' % 
1055                                     (claimed_id,), None) 
1056          return self._verifyDiscoveredServices(claimed_id, services, 
1057                                                to_match_endpoints) 
 1058   
1059   
1061          """See @L{_discoverAndVerify}""" 
1062   
1063           
1064           
1065          failure_messages = [] 
1066          for endpoint in services: 
1067              for to_match_endpoint in to_match_endpoints: 
1068                  try: 
1069                      self._verifyDiscoverySingle( 
1070                          endpoint, to_match_endpoint) 
1071                  except ProtocolError, why: 
1072                      failure_messages.append(str(why)) 
1073                  else: 
1074                       
1075                       
1076                      return endpoint 
1077          else: 
1078              oidutil.log('Discovery verification failure for %s' % 
1079                          (claimed_id,)) 
1080              for failure_message in failure_messages: 
1081                  oidutil.log(' * Endpoint mismatch: ' + failure_message) 
1082   
1083              raise DiscoveryFailure( 
1084                  'No matching endpoint found after discovering %s' 
1085                  % (claimed_id,), None) 
 1086   
1088          """Make a check_authentication request to verify this message. 
1089   
1090          @returns: True if the request is valid. 
1091          @rtype: bool 
1092          """ 
1093          oidutil.log('Using OpenID check_authentication') 
1094          request = self._createCheckAuthRequest(message) 
1095          if request is None: 
1096              return False 
1097          try: 
1098              response = self._makeKVPost(request, server_url) 
1099          except (fetchers.HTTPFetchingError, ServerError), e: 
1100              oidutil.log('check_authentication failed: %s' % (e[0],)) 
1101              return False 
1102          else: 
1103              return self._processCheckAuthResponse(response, server_url) 
 1104   
1106          """Generate a check_authentication request message given an 
1107          id_res message. 
1108          """ 
1109          signed = message.getArg(OPENID_NS, 'signed') 
1110          if signed: 
1111              for k in signed.split(','): 
1112                  oidutil.log(k) 
1113                  val = message.getAliasedArg(k) 
1114   
1115                   
1116                  if val is None: 
1117                      oidutil.log('Missing signed field %r' % (k,)) 
1118                      return None 
1119   
1120          check_auth_message = message.copy() 
1121          check_auth_message.setArg(OPENID_NS, 'mode', 'check_authentication') 
1122          return check_auth_message 
 1123   
1125          """Process the response message from a check_authentication 
1126          request, invalidating associations if requested. 
1127          """ 
1128          is_valid = response.getArg(OPENID_NS, 'is_valid', 'false') 
1129   
1130          invalidate_handle = response.getArg(OPENID_NS, 'invalidate_handle') 
1131          if invalidate_handle is not None: 
1132              oidutil.log( 
1133                  'Received "invalidate_handle" from server %s' % (server_url,)) 
1134              if self.store is None: 
1135                  oidutil.log('Unexpectedly got invalidate_handle without ' 
1136                              'a store!') 
1137              else: 
1138                  self.store.removeAssociation(server_url, invalidate_handle) 
1139   
1140          if is_valid == 'true': 
1141              return True 
1142          else: 
1143              oidutil.log('Server responds that checkAuth call is not valid') 
1144              return False 
 1145   
1147          """Get an association for the endpoint's server_url. 
1148   
1149          First try seeing if we have a good association in the 
1150          store. If we do not, then attempt to negotiate an association 
1151          with the server. 
1152   
1153          If we negotiate a good association, it will get stored. 
1154   
1155          @returns: A valid association for the endpoint's server_url or None 
1156          @rtype: openid.association.Association or NoneType 
1157          """ 
1158          assoc = self.store.getAssociation(endpoint.server_url) 
1159   
1160          if assoc is None or assoc.expiresIn <= 0: 
1161              assoc = self._negotiateAssociation(endpoint) 
1162              if assoc is not None: 
1163                  self.store.storeAssociation(endpoint.server_url, assoc) 
1164   
1165          return assoc 
 1166   
1168          """Make association requests to the server, attempting to 
1169          create a new association. 
1170   
1171          @returns: a new association object 
1172   
1173          @rtype: L{openid.association.Association} 
1174          """ 
1175           
1176          assoc_type, session_type = self.negotiator.getAllowedType() 
1177   
1178          try: 
1179              assoc = self._requestAssociation( 
1180                  endpoint, assoc_type, session_type) 
1181          except ServerError, why: 
1182              supportedTypes = self._extractSupportedAssociationType(why, 
1183                                                                     endpoint, 
1184                                                                     assoc_type) 
1185              if supportedTypes is not None: 
1186                  assoc_type, session_type = supportedTypes 
1187                   
1188                   
1189                   
1190                  try: 
1191                      assoc = self._requestAssociation( 
1192                          endpoint, assoc_type, session_type) 
1193                  except ServerError, why: 
1194                       
1195                       
1196                      oidutil.log('Server %s refused its suggested association ' 
1197                                  'type: session_type=%s, assoc_type=%s' 
1198                                  % (endpoint.server_url, session_type, 
1199                                     assoc_type)) 
1200                      return None 
1201                  else: 
1202                      return assoc 
1203          else: 
1204              return assoc 
 1205   
1208          """Handle ServerErrors resulting from association requests. 
1209   
1210          @returns: If server replied with an C{unsupported-type} error, 
1211              return a tuple of supported C{association_type}, C{session_type}. 
1212              Otherwise logs the error and returns None. 
1213          @rtype: tuple or None 
1214          """ 
1215           
1216           
1217          if server_error.error_code != 'unsupported-type' or \ 
1218                 server_error.message.isOpenID1(): 
1219              oidutil.log( 
1220                  'Server error when requesting an association from %r: %s' 
1221                  % (endpoint.server_url, server_error.error_text)) 
1222              return None 
1223   
1224           
1225           
1226           
1227          oidutil.log( 
1228              'Unsupported association type %s: %s' % (assoc_type, 
1229                                                       server_error.error_text,)) 
1230   
1231           
1232           
1233          assoc_type = server_error.message.getArg(OPENID_NS, 'assoc_type') 
1234          session_type = server_error.message.getArg(OPENID_NS, 'session_type') 
1235   
1236          if assoc_type is None or session_type is None: 
1237              oidutil.log('Server responded with unsupported association ' 
1238                          'session but did not supply a fallback.') 
1239              return None 
1240          elif not self.negotiator.isAllowed(assoc_type, session_type): 
1241              fmt = ('Server sent unsupported session/association type: ' 
1242                     'session_type=%s, assoc_type=%s') 
1243              oidutil.log(fmt % (session_type, assoc_type)) 
1244              return None 
1245          else: 
1246              return assoc_type, session_type 
 1247   
1248   
1250          """Make and process one association request to this endpoint's 
1251          OP endpoint URL. 
1252   
1253          @returns: An association object or None if the association 
1254              processing failed. 
1255   
1256          @raises ServerError: when the remote OpenID server returns an error. 
1257          """ 
1258          assoc_session, args = self._createAssociateRequest( 
1259              endpoint, assoc_type, session_type) 
1260   
1261          try: 
1262              response = self._makeKVPost(args, endpoint.server_url) 
1263          except fetchers.HTTPFetchingError, why: 
1264              oidutil.log('openid.associate request failed: %s' % (why[0],)) 
1265              return None 
1266   
1267          try: 
1268              assoc = self._extractAssociation(response, assoc_session) 
1269          except KeyError, why: 
1270              oidutil.log('Missing required parameter in response from %s: %s' 
1271                          % (endpoint.server_url, why[0])) 
1272              return None 
1273          except ProtocolError, why: 
1274              oidutil.log('Protocol error parsing response from %s: %s' % ( 
1275                  endpoint.server_url, why[0])) 
1276              return None 
1277          else: 
1278              return assoc 
 1279   
1281          """Create an association request for the given assoc_type and 
1282          session_type. 
1283   
1284          @param endpoint: The endpoint whose server_url will be 
1285              queried. The important bit about the endpoint is whether 
1286              it's in compatiblity mode (OpenID 1.1) 
1287   
1288          @param assoc_type: The association type that the request 
1289              should ask for. 
1290          @type assoc_type: str 
1291   
1292          @param session_type: The session type that should be used in 
1293              the association request. The session_type is used to 
1294              create an association session object, and that session 
1295              object is asked for any additional fields that it needs to 
1296              add to the request. 
1297          @type session_type: str 
1298   
1299          @returns: a pair of the association session object and the 
1300              request message that will be sent to the server. 
1301          @rtype: (association session type (depends on session_type), 
1302                   openid.message.Message) 
1303          """ 
1304          session_type_class = self.session_types[session_type] 
1305          assoc_session = session_type_class() 
1306   
1307          args = { 
1308              'mode': 'associate', 
1309              'assoc_type': assoc_type, 
1310              } 
1311   
1312          if not endpoint.compatibilityMode(): 
1313              args['ns'] = OPENID2_NS 
1314   
1315           
1316           
1317          if (not endpoint.compatibilityMode() or 
1318              assoc_session.session_type != 'no-encryption'): 
1319              args['session_type'] = assoc_session.session_type 
1320   
1321          args.update(assoc_session.getRequest()) 
1322          message = Message.fromOpenIDArgs(args) 
1323          return assoc_session, message 
 1324   
1326          """Given an association response message, extract the OpenID 
1327          1.X session type. 
1328   
1329          This function mostly takes care of the 'no-encryption' default 
1330          behavior in OpenID 1. 
1331   
1332          If the association type is plain-text, this function will 
1333          return 'no-encryption' 
1334   
1335          @returns: The association type for this message 
1336          @rtype: str 
1337   
1338          @raises KeyError: when the session_type field is absent. 
1339          """ 
1340           
1341           
1342          session_type = assoc_response.getArg(OPENID1_NS, 'session_type') 
1343   
1344           
1345           
1346   
1347           
1348           
1349           
1350          if session_type == 'no-encryption': 
1351              oidutil.log('WARNING: OpenID server sent "no-encryption"' 
1352                          'for OpenID 1.X') 
1353   
1354           
1355           
1356           
1357           
1358          elif session_type == '' or session_type is None: 
1359              session_type = 'no-encryption' 
1360   
1361          return session_type 
 1362   
1364          """Attempt to extract an association from the response, given 
1365          the association response message and the established 
1366          association session. 
1367   
1368          @param assoc_response: The association response message from 
1369              the server 
1370          @type assoc_response: openid.message.Message 
1371   
1372          @param assoc_session: The association session object that was 
1373              used when making the request 
1374          @type assoc_session: depends on the session type of the request 
1375   
1376          @raises ProtocolError: when data is malformed 
1377          @raises KeyError: when a field is missing 
1378   
1379          @rtype: openid.association.Association 
1380          """ 
1381           
1382           
1383          assoc_type = assoc_response.getArg( 
1384              OPENID_NS, 'assoc_type', no_default) 
1385          assoc_handle = assoc_response.getArg( 
1386              OPENID_NS, 'assoc_handle', no_default) 
1387   
1388           
1389           
1390           
1391           
1392          expires_in_str = assoc_response.getArg( 
1393              OPENID_NS, 'expires_in', no_default) 
1394          try: 
1395              expires_in = int(expires_in_str) 
1396          except ValueError, why: 
1397              raise ProtocolError('Invalid expires_in field: %s' % (why[0],)) 
1398   
1399           
1400          if assoc_response.isOpenID1(): 
1401              session_type = self._getOpenID1SessionType(assoc_response) 
1402          else: 
1403              session_type = assoc_response.getArg( 
1404                  OPENID2_NS, 'session_type', no_default) 
1405   
1406           
1407          if assoc_session.session_type != session_type: 
1408              if (assoc_response.isOpenID1() and 
1409                  session_type == 'no-encryption'): 
1410                   
1411                   
1412                   
1413                   
1414                   
1415                  assoc_session = PlainTextConsumerSession() 
1416              else: 
1417                   
1418                   
1419                   
1420                  fmt = 'Session type mismatch. Expected %r, got %r' 
1421                  message = fmt % (assoc_session.session_type, session_type) 
1422                  raise ProtocolError(message) 
1423   
1424           
1425          if assoc_type not in assoc_session.allowed_assoc_types: 
1426              fmt = 'Unsupported assoc_type for session %s returned: %s' 
1427              raise ProtocolError(fmt % (assoc_session.session_type, assoc_type)) 
1428   
1429           
1430           
1431           
1432          try: 
1433              secret = assoc_session.extractSecret(assoc_response) 
1434          except ValueError, why: 
1435              fmt = 'Malformed response for %s session: %s' 
1436              raise ProtocolError(fmt % (assoc_session.session_type, why[0])) 
1437   
1438          return Association.fromExpiresIn( 
1439              expires_in, assoc_handle, secret, assoc_type) 
  1440   
1442      """An object that holds the state necessary for generating an 
1443      OpenID authentication request. This object holds the association 
1444      with the server and the discovered information with which the 
1445      request will be made. 
1446   
1447      It is separate from the consumer because you may wish to add 
1448      things to the request before sending it on its way to the 
1449      server. It also has serialization options that let you encode the 
1450      authentication request as a URL or as a form POST. 
1451      """ 
1452   
1454          """ 
1455          Creates a new AuthRequest object.  This just stores each 
1456          argument in an appropriately named field. 
1457   
1458          Users of this library should not create instances of this 
1459          class.  Instances of this class are created by the library 
1460          when needed. 
1461          """ 
1462          self.assoc = assoc 
1463          self.endpoint = endpoint 
1464          self.return_to_args = {} 
1465          self.message = Message(endpoint.preferredNamespace()) 
1466          self._anonymous = False 
 1467   
1469          """Set whether this request should be made anonymously. If a 
1470          request is anonymous, the identifier will not be sent in the 
1471          request. This is only useful if you are making another kind of 
1472          request with an extension in this request. 
1473   
1474          Anonymous requests are not allowed when the request is made 
1475          with OpenID 1. 
1476   
1477          @raises ValueError: when attempting to set an OpenID1 request 
1478              as anonymous 
1479          """ 
1480          if is_anonymous and self.message.isOpenID1(): 
1481              raise ValueError('OpenID 1 requests MUST include the ' 
1482                               'identifier in the request') 
1483          else: 
1484              self._anonymous = is_anonymous 
 1485   
1487          """Add an extension to this checkid request. 
1488   
1489          @param extension_request: An object that implements the 
1490              extension interface for adding arguments to an OpenID 
1491              message. 
1492          """ 
1493          extension_request.toMessage(self.message) 
 1494   
1496          """Add an extension argument to this OpenID authentication 
1497          request. 
1498   
1499          Use caution when adding arguments, because they will be 
1500          URL-escaped and appended to the redirect URL, which can easily 
1501          get quite long. 
1502   
1503          @param namespace: The namespace for the extension. For 
1504              example, the simple registration extension uses the 
1505              namespace C{sreg}. 
1506   
1507          @type namespace: str 
1508   
1509          @param key: The key within the extension namespace. For 
1510              example, the nickname field in the simple registration 
1511              extension's key is C{nickname}. 
1512   
1513          @type key: str 
1514   
1515          @param value: The value to provide to the server for this 
1516              argument. 
1517   
1518          @type value: str 
1519          """ 
1520          self.message.setArg(namespace, key, value) 
 1521   
1522 -    def getMessage(self, realm, return_to=None, immediate=False): 
 1523          """Produce a L{openid.message.Message} representing this request. 
1524   
1525          @param realm: The URL (or URL pattern) that identifies your 
1526              web site to the user when she is authorizing it. 
1527   
1528          @type realm: str 
1529   
1530          @param return_to: The URL that the OpenID provider will send the 
1531              user back to after attempting to verify her identity. 
1532   
1533              Not specifying a return_to URL means that the user will not 
1534              be returned to the site issuing the request upon its 
1535              completion. 
1536   
1537          @type return_to: str 
1538   
1539          @param immediate: If True, the OpenID provider is to send back 
1540              a response immediately, useful for behind-the-scenes 
1541              authentication attempts.  Otherwise the OpenID provider 
1542              may engage the user before providing a response.  This is 
1543              the default case, as the user may need to provide 
1544              credentials or approve the request before a positive 
1545              response can be sent. 
1546   
1547          @type immediate: bool 
1548   
1549          @returntype: L{openid.message.Message} 
1550          """ 
1551          if return_to: 
1552              return_to = oidutil.appendArgs(return_to, self.return_to_args) 
1553          elif immediate: 
1554              raise ValueError( 
1555                  '"return_to" is mandatory when using "checkid_immediate"') 
1556          elif self.message.isOpenID1(): 
1557              raise ValueError('"return_to" is mandatory for OpenID 1 requests') 
1558          elif self.return_to_args: 
1559              raise ValueError('extra "return_to" arguments were specified, ' 
1560                               'but no return_to was specified') 
1561   
1562          if immediate: 
1563              mode = 'checkid_immediate' 
1564          else: 
1565              mode = 'checkid_setup' 
1566   
1567          message = self.message.copy() 
1568          if message.isOpenID1(): 
1569              realm_key = 'trust_root' 
1570          else: 
1571              realm_key = 'realm' 
1572   
1573          message.updateArgs(OPENID_NS, 
1574              { 
1575              realm_key:realm, 
1576              'mode':mode, 
1577              'return_to':return_to, 
1578              }) 
1579   
1580          if not self._anonymous: 
1581              if self.endpoint.isOPIdentifier(): 
1582                   
1583                   
1584                   
1585                  claimed_id = request_identity = IDENTIFIER_SELECT 
1586              else: 
1587                  request_identity = self.endpoint.getLocalID() 
1588                  claimed_id = self.endpoint.claimed_id 
1589   
1590               
1591              message.setArg(OPENID_NS, 'identity', request_identity) 
1592   
1593              if message.isOpenID2(): 
1594                  message.setArg(OPENID2_NS, 'claimed_id', claimed_id) 
1595   
1596          if self.assoc: 
1597              message.setArg(OPENID_NS, 'assoc_handle', self.assoc.handle) 
1598              assoc_log_msg = 'with assocication %s' % (self.assoc.handle,) 
1599          else: 
1600              assoc_log_msg = 'using stateless mode.' 
1601   
1602          oidutil.log("Generated %s request to %s %s" % 
1603                      (mode, self.endpoint.server_url, assoc_log_msg)) 
1604   
1605          return message 
 1606   
1607 -    def redirectURL(self, realm, return_to=None, immediate=False): 
 1608          """Returns a URL with an encoded OpenID request. 
1609   
1610          The resulting URL is the OpenID provider's endpoint URL with 
1611          parameters appended as query arguments.  You should redirect 
1612          the user agent to this URL. 
1613   
1614          OpenID 2.0 endpoints also accept POST requests, see 
1615          C{L{shouldSendRedirect}} and C{L{formMarkup}}. 
1616   
1617          @param realm: The URL (or URL pattern) that identifies your 
1618              web site to the user when she is authorizing it. 
1619   
1620          @type realm: str 
1621   
1622          @param return_to: The URL that the OpenID provider will send the 
1623              user back to after attempting to verify her identity. 
1624   
1625              Not specifying a return_to URL means that the user will not 
1626              be returned to the site issuing the request upon its 
1627              completion. 
1628   
1629          @type return_to: str 
1630   
1631          @param immediate: If True, the OpenID provider is to send back 
1632              a response immediately, useful for behind-the-scenes 
1633              authentication attempts.  Otherwise the OpenID provider 
1634              may engage the user before providing a response.  This is 
1635              the default case, as the user may need to provide 
1636              credentials or approve the request before a positive 
1637              response can be sent. 
1638   
1639          @type immediate: bool 
1640   
1641          @returns: The URL to redirect the user agent to. 
1642   
1643          @returntype: str 
1644          """ 
1645          message = self.getMessage(realm, return_to, immediate) 
1646          return message.toURL(self.endpoint.server_url) 
 1647   
1661   
1662 -    def htmlMarkup(self, realm, return_to=None, immediate=False, 
1663              form_tag_attrs=None): 
 1664          """Get an autosubmitting HTML page that submits this request to the 
1665          IDP.  This is just a wrapper for formMarkup. 
1666   
1667          @see: formMarkup 
1668   
1669          @returns: str 
1670          """ 
1671          return oidutil.autoSubmitHTML(self.formMarkup(realm,  
1672                                                        return_to, 
1673                                                        immediate,  
1674                                                        form_tag_attrs)) 
 1675   
1677          """Should this OpenID authentication request be sent as a HTTP 
1678          redirect or as a POST (form submission)? 
1679   
1680          @rtype: bool 
1681          """ 
1682          return self.endpoint.compatibilityMode() 
  1683   
1684  FAILURE = 'failure' 
1685  SUCCESS = 'success' 
1686  CANCEL = 'cancel' 
1687  SETUP_NEEDED = 'setup_needed' 
1688   
1690      status = None 
1691   
1698   
1700          """Return the display identifier for this response. 
1701   
1702          The display identifier is related to the Claimed Identifier, but the 
1703          two are not always identical.  The display identifier is something the 
1704          user should recognize as what they entered, whereas the response's 
1705          claimed identifier (in the L{identity_url} attribute) may have extra 
1706          information for better persistence. 
1707   
1708          URLs will be stripped of their fragments for display.  XRIs will 
1709          display the human-readable identifier (i-name) instead of the 
1710          persistent identifier (i-number). 
1711   
1712          Use the display identifier in your user interface.  Use 
1713          L{identity_url} for querying your database or authorization server. 
1714          """ 
1715          if self.endpoint is not None: 
1716              return self.endpoint.getDisplayIdentifier() 
1717          return None 
  1718   
1720      """A response with a status of SUCCESS. Indicates that this request is a 
1721      successful acknowledgement from the OpenID server that the 
1722      supplied URL is, indeed controlled by the requesting agent. 
1723   
1724      @ivar identity_url: The identity URL that has been authenticated; the Claimed Identifier. 
1725          See also L{getDisplayIdentifier}. 
1726   
1727      @ivar endpoint: The endpoint that authenticated the identifier.  You 
1728          may access other discovered information related to this endpoint, 
1729          such as the CanonicalID of an XRI, through this object. 
1730      @type endpoint: L{OpenIDServiceEndpoint<openid.consumer.discover.OpenIDServiceEndpoint>} 
1731   
1732      @ivar signed_fields: The arguments in the server's response that 
1733          were signed and verified. 
1734   
1735      @cvar status: SUCCESS 
1736      """ 
1737   
1738      status = SUCCESS 
1739   
1740 -    def __init__(self, endpoint, message, signed_fields=None): 
 1751   
1753          """Was this authentication response an OpenID 1 authentication 
1754          response? 
1755          """ 
1756          return self.message.isOpenID1() 
 1757   
1759          """Return whether a particular key is signed, regardless of 
1760          its namespace alias 
1761          """ 
1762          return self.message.getKey(ns_uri, ns_key) in self.signed_fields 
 1763   
1764 -    def getSigned(self, ns_uri, ns_key, default=None): 
 1765          """Return the specified signed field if available, 
1766          otherwise return default 
1767          """ 
1768          if self.isSigned(ns_uri, ns_key): 
1769              return self.message.getArg(ns_uri, ns_key, default) 
1770          else: 
1771              return default 
 1772   
1774          """Get signed arguments from the response message.  Return a 
1775          dict of all arguments in the specified namespace.  If any of 
1776          the arguments are not signed, return None. 
1777          """ 
1778          msg_args = self.message.getArgs(ns_uri) 
1779   
1780          for key in msg_args.iterkeys(): 
1781              if not self.isSigned(ns_uri, key): 
1782                  oidutil.log("SuccessResponse.getSignedNS: (%s, %s) not signed." 
1783                              % (ns_uri, key)) 
1784                  return None 
1785   
1786          return msg_args 
 1787   
1789          """Return response arguments in the specified namespace. 
1790   
1791          @param namespace_uri: The namespace URI of the arguments to be 
1792          returned. 
1793   
1794          @param require_signed: True if the arguments should be among 
1795          those signed in the response, False if you don't care. 
1796   
1797          If require_signed is True and the arguments are not signed, 
1798          return None. 
1799          """ 
1800          if require_signed: 
1801              return self.getSignedNS(namespace_uri) 
1802          else: 
1803              return self.message.getArgs(namespace_uri) 
 1804   
1806          """Get the openid.return_to argument from this response. 
1807   
1808          This is useful for verifying that this request was initiated 
1809          by this consumer. 
1810   
1811          @returns: The return_to URL supplied to the server on the 
1812              initial request, or C{None} if the response did not contain 
1813              an C{openid.return_to} argument. 
1814   
1815          @returntype: str 
1816          """ 
1817          return self.getSigned(OPENID_NS, 'return_to') 
 1818   
1826   
1828          return not (self == other) 
 1829   
1831          return '<%s.%s id=%r signed=%r>' % ( 
1832              self.__class__.__module__, 
1833              self.__class__.__name__, 
1834              self.identity_url, self.signed_fields) 
  1835   
1836   
1838      """A response with a status of FAILURE. Indicates that the OpenID 
1839      protocol has failed. This could be locally or remotely triggered. 
1840   
1841      @ivar identity_url:  The identity URL for which authenitcation was 
1842          attempted, if it can be determined. Otherwise, None. 
1843   
1844      @ivar message: A message indicating why the request failed, if one 
1845          is supplied. otherwise, None. 
1846   
1847      @cvar status: FAILURE 
1848      """ 
1849   
1850      status = FAILURE 
1851   
1852 -    def __init__(self, endpoint, message=None, contact=None, 
1853                   reference=None): 
 1854          self.setEndpoint(endpoint) 
1855          self.message = message 
1856          self.contact = contact 
1857          self.reference = reference 
 1858   
1860          return "<%s.%s id=%r message=%r>" % ( 
1861              self.__class__.__module__, self.__class__.__name__, 
1862              self.identity_url, self.message) 
  1863   
1864   
1866      """A response with a status of CANCEL. Indicates that the user 
1867      cancelled the OpenID authentication request. 
1868   
1869      @ivar identity_url: The identity URL for which authenitcation was 
1870          attempted, if it can be determined. Otherwise, None. 
1871   
1872      @cvar status: CANCEL 
1873      """ 
1874   
1875      status = CANCEL 
1876   
 1879   
1881      """A response with a status of SETUP_NEEDED. Indicates that the 
1882      request was in immediate mode, and the server is unable to 
1883      authenticate the user without further interaction. 
1884   
1885      @ivar identity_url:  The identity URL for which authenitcation was 
1886          attempted. 
1887   
1888      @ivar setup_url: A URL that can be used to send the user to the 
1889          server to set up for authentication. The user should be 
1890          redirected in to the setup_url, either in the current window 
1891          or in a new browser window.  C{None} in OpenID 2.0. 
1892   
1893      @cvar status: SETUP_NEEDED 
1894      """ 
1895   
1896      status = SETUP_NEEDED 
1897   
1898 -    def __init__(self, endpoint, setup_url=None): 
 1899          self.setEndpoint(endpoint) 
1900          self.setup_url = setup_url 
  1901