Support Joomla!

Packages

Package: OpenID

License

Content on this site is copyright © 2005 - 2008 Open Source Matters Inc and can be used in accordance with the Joomla! Electronic Documentation License. Some parts of this website may be subject to other licenses.
Source code for file /openid/Auth/OpenID/Consumer.php

Documentation is available at Consumer.php

  1. <?php
  2.  
  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
  6.  * isn't documented in full here is the store required to create an
  7.  * Auth_OpenID_Consumer instance.  More on the abstract store type and
  8.  * concrete implementations of it that are provided in the
  9.  * documentation for the Auth_OpenID_Consumer constructor.
  10.  *
  11.  * OVERVIEW
  12.  *
  13.  * The OpenID identity verification process most commonly uses the
  14.  * following steps, as visible to the user of this library:
  15.  *
  16.  *   1. The user enters their OpenID into a field on the consumer's
  17.  *      site, and hits a login button.
  18.  *   2. The consumer site discovers the user's OpenID server using the
  19.  *      YADIS protocol.
  20.  *   3. The consumer site sends the browser a redirect to the identity
  21.  *      server.  This is the authentication request as described in
  22.  *      the OpenID specification.
  23.  *   4. The identity server's site sends the browser a redirect back
  24.  *      to the consumer site.  This redirect contains the server's
  25.  *      response to the authentication request.
  26.  *
  27.  * The most important part of the flow to note is the consumer's site
  28.  * must handle two separate HTTP requests in order to perform the full
  29.  * identity check.
  30.  *
  31.  * LIBRARY DESIGN
  32.  * 
  33.  * This consumer library is designed with that flow in mind.  The goal
  34.  * is to make it as easy as possible to perform the above steps
  35.  * securely.
  36.  *
  37.  * At a high level, there are two important parts in the consumer
  38.  * library.  The first important part is this module, which contains
  39.  * the interface to actually use this library.  The second is the
  40.  * Auth_OpenID_Interface class, which describes the interface to use
  41.  * if you need to create a custom method for storing the state this
  42.  * library needs to maintain between requests.
  43.  *
  44.  * In general, the second part is less important for users of the
  45.  * library to know about, as several implementations are provided
  46.  * which cover a wide variety of situations in which consumers may use
  47.  * the library.
  48.  *
  49.  * This module contains a class, Auth_OpenID_Consumer, with methods
  50.  * corresponding to the actions necessary in each of steps 2, 3, and 4
  51.  * described in the overview.  Use of this library should be as easy
  52.  * as creating an Auth_OpenID_Consumer instance and calling the
  53.  * methods appropriate for the action the site wants to take.
  54.  *
  55.  * STORES AND DUMB MODE
  56.  *
  57.  * OpenID is a protocol that works best when the consumer site is able
  58.  * to store some state.  This is the normal mode of operation for the
  59.  * protocol, and is sometimes referred to as smart mode.  There is
  60.  * also a fallback mode, known as dumb mode, which is available when
  61.  * the consumer site is not able to store state.  This mode should be
  62.  * avoided when possible, as it leaves the implementation more
  63.  * vulnerable to replay attacks.
  64.  *
  65.  * The mode the library works in for normal operation is determined by
  66.  * the store that it is given.  The store is an abstraction that
  67.  * handles the data that the consumer needs to manage between http
  68.  * requests in order to operate efficiently and securely.
  69.  *
  70.  * Several store implementation are provided, and the interface is
  71.  * fully documented so that custom stores can be used as well.  See
  72.  * the documentation for the Auth_OpenID_Consumer class for more
  73.  * information on the interface for stores.  The implementations that
  74.  * are provided allow the consumer site to store the necessary data in
  75.  * several different ways, including several SQL databases and normal
  76.  * files on disk.
  77.  *
  78.  * There is an additional concrete store provided that puts the system
  79.  * in dumb mode.  This is not recommended, as it removes the library's
  80.  * ability to stop replay attacks reliably.  It still uses time-based
  81.  * checking to make replay attacks only possible within a small
  82.  * window, but they remain possible within that window.  This store
  83.  * should only be used if the consumer site has no way to retain data
  84.  * between requests at all.
  85.  *
  86.  * IMMEDIATE MODE
  87.  *
  88.  * In the flow described above, the user may need to confirm to the
  89.  * lidentity server that it's ok to authorize his or her identity.
  90.  * The server may draw pages asking for information from the user
  91.  * before it redirects the browser back to the consumer's site.  This
  92.  * is generally transparent to the consumer site, so it is typically
  93.  * ignored as an implementation detail.
  94.  *
  95.  * There can be times, however, where the consumer site wants to get a
  96.  * response immediately.  When this is the case, the consumer can put
  97.  * the library in immediate mode.  In immediate mode, there is an
  98.  * extra response possible from the server, which is essentially the
  99.  * server reporting that it doesn't have enough information to answer
  100.  * the question yet.
  101.  *
  102.  * USING THIS LIBRARY
  103.  *
  104.  * Integrating this library into an application is usually a
  105.  * relatively straightforward process.  The process should basically
  106.  * follow this plan:
  107.  *
  108.  * Add an OpenID login field somewhere on your site.  When an OpenID
  109.  * is entered in that field and the form is submitted, it should make
  110.  * a request to the your site which includes that OpenID URL.
  111.  *
  112.  * First, the application should instantiate the Auth_OpenID_Consumer
  113.  * class using the store of choice (Auth_OpenID_FileStore or one of
  114.  * the SQL-based stores).  If the application has a custom
  115.  * session-management implementation, an object implementing the
  116.  * {@link Auth_Yadis_PHPSession} interface should be passed as the
  117.  * second parameter.  Otherwise, the default uses $_SESSION.
  118.  *
  119.  * Next, the application should call the Auth_OpenID_Consumer object's
  120.  * 'begin' method.  This method takes the OpenID URL.  The 'begin'
  121.  * method returns an Auth_OpenID_AuthRequest object.
  122.  *
  123.  * Next, the application should call the 'redirectURL' method of the
  124.  * Auth_OpenID_AuthRequest object.  The 'return_to' URL parameter is
  125.  * the URL that the OpenID server will send the user back to after
  126.  * attempting to verify his or her identity.  The 'trust_root' is the
  127.  * URL (or URL pattern) that identifies your web site to the user when
  128.  * he or she is authorizing it.  Send a redirect to the resulting URL
  129.  * to the user's browser.
  130.  *
  131.  * That's the first half of the authentication process.  The second
  132.  * half of the process is done after the user's ID server sends the
  133.  * user's browser a redirect back to your site to complete their
  134.  * login.
  135.  *
  136.  * When that happens, the user will contact your site at the URL given
  137.  * as the 'return_to' URL to the Auth_OpenID_AuthRequest::redirectURL
  138.  * call made above.  The request will have several query parameters
  139.  * added to the URL by the identity server as the information
  140.  * necessary to finish the request.
  141.  *
  142.  * Lastly, instantiate an Auth_OpenID_Consumer instance as above and
  143.  * call its 'complete' method, passing in all the received query
  144.  * arguments.
  145.  *
  146.  * There are multiple possible return types possible from that
  147.  * method. These indicate the whether or not the login was successful,
  148.  * and include any additional information appropriate for their type.
  149.  *
  150.  * PHP versions 4 and 5
  151.  *
  152.  * LICENSE: See the COPYING file included in this distribution.
  153.  *
  154.  * @package OpenID
  155.  * @author JanRain, Inc. <openid@janrain.com>
  156.  * @copyright 2005-2008 Janrain, Inc.
  157.  * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
  158.  */
  159.  
  160. // Do not allow direct access
  161. defined'_JEXEC' or die'Restricted access' );
  162.  
  163. /**
  164.  * Require utility classes and functions for the consumer.
  165.  */
  166. require_once "Auth/OpenID.php";
  167. require_once "Auth/OpenID/Message.php";
  168. require_once "Auth/OpenID/HMAC.php";
  169. require_once "Auth/OpenID/Association.php";
  170. require_once "Auth/OpenID/CryptUtil.php";
  171. require_once "Auth/OpenID/DiffieHellman.php";
  172. require_once "Auth/OpenID/KVForm.php";
  173. require_once "Auth/OpenID/Nonce.php";
  174. require_once "Auth/OpenID/Discover.php";
  175. require_once "Auth/OpenID/URINorm.php";
  176. require_once "Auth/Yadis/Manager.php";
  177. require_once "Auth/Yadis/XRI.php";
  178.  
  179. /**
  180.  * This is the status code returned when the complete method returns
  181.  * successfully.
  182.  */
  183. define('Auth_OpenID_SUCCESS''success');
  184.  
  185. /**
  186.  * Status to indicate cancellation of OpenID authentication.
  187.  */
  188. define('Auth_OpenID_CANCEL''cancel');
  189.  
  190. /**
  191.  * This is the status code completeAuth returns when the value it
  192.  * received indicated an invalid login.
  193.  */
  194. define('Auth_OpenID_FAILURE''failure');
  195.  
  196. /**
  197.  * This is the status code completeAuth returns when the
  198.  * {@link Auth_OpenID_Consumer} instance is in immediate mode, and the
  199.  * identity server sends back a URL to send the user to to complete his
  200.  * or her login.
  201.  */
  202. define('Auth_OpenID_SETUP_NEEDED''setup needed');
  203.  
  204. /**
  205.  * This is the status code beginAuth returns when the page fetched
  206.  * from the entered OpenID URL doesn't contain the necessary link tags
  207.  * to function as an identity page.
  208.  */
  209. define('Auth_OpenID_PARSE_ERROR''parse error');
  210.  
  211. /**
  212.  * An OpenID consumer implementation that performs discovery and does
  213.  * session management.  See the Consumer.php file documentation for
  214.  * more information.
  215.  *
  216.  * @package OpenID
  217.  */
  218.  
  219.     /**
  220.      * @access private
  221.      */
  222.     var $discoverMethod 'Auth_OpenID_discover';
  223.  
  224.     /**
  225.      * @access private
  226.      */
  227.     var $session_key_prefix "_openid_consumer_";
  228.  
  229.     /**
  230.      * @access private
  231.      */
  232.     var $_token_suffix "last_token";
  233.  
  234.     /**
  235.      * Initialize a Consumer instance.
  236.      *
  237.      * You should create a new instance of the Consumer object with
  238.      * every HTTP request that handles OpenID transactions.
  239.      *
  240.      * @param Auth_OpenID_OpenIDStore $store This must be an object
  241.      *  that implements the interface in {@link }
  242.      *  Auth_OpenID_OpenIDStore}.  Several concrete implementations are
  243.      *  provided, to cover most common use cases.  For stores backed by
  244.      *  MySQL, PostgreSQL, or SQLite, see the {@link }
  245.      *  Auth_OpenID_SQLStore} class and its sublcasses.  For a
  246.      *  filesystem-backed store, see the {@link Auth_OpenID_FileStore}
  247.      *  module.  As a last resort, if it isn't possible for the server
  248.      *  to store state at all, an instance of {@link }
  249.      *  Auth_OpenID_DumbStore} can be used.
  250.      *
  251.      * @param mixed $session An object which implements the interface
  252.      *  of the {@link Auth_Yadis_PHPSession} class.  Particularly, this
  253.      *  object is expected to have these methods: get($key), set($key),
  254.      *  $value), and del($key).  This defaults to a session object
  255.      *  which wraps PHP's native session machinery.  You should only
  256.      *  need to pass something here if you have your own sessioning
  257.      *  implementation.
  258.      *
  259.      * @param str $consumer_cls The name of the class to instantiate
  260.      *  when creating the internal consumer object.  This is used for
  261.      *  testing.
  262.      */
  263.     function Auth_OpenID_Consumer(&$store$session null,
  264.                                   $consumer_cls null)
  265.     {
  266.         if ($session === null{
  267.             $session new Auth_Yadis_PHPSession();
  268.         }
  269.  
  270.         $this->session =$session;
  271.  
  272.         if ($consumer_cls !== null{
  273.             $this->consumer =new $consumer_cls($store);
  274.         else {
  275.             $this->consumer =new Auth_OpenID_GenericConsumer($store);
  276.         }
  277.  
  278.         $this->_token_key $this->session_key_prefix $this->_token_suffix;
  279.     }
  280.  
  281.     /**
  282.      * Used in testing to define the discovery mechanism.
  283.      *
  284.      * @access private
  285.      */
  286.     function getDiscoveryObject(&$session$openid_url,
  287.                                 $session_key_prefix)
  288.     {
  289.         return new Auth_Yadis_Discovery($session$openid_url,
  290.                                         $session_key_prefix);
  291.     }
  292.  
  293.     /**
  294.      * Start the OpenID authentication process. See steps 1-2 in the
  295.      * overview at the top of this file.
  296.      *
  297.      * @param string $user_url Identity URL given by the user. This
  298.      *  method performs a textual transformation of the URL to try and
  299.      *  make sure it is normalized. For example, a user_url of
  300.      *  example.com will be normalized to http://example.com/
  301.      *  normalizing and resolving any redirects the server might issue.
  302.      *
  303.      * @param bool $anonymous True if the OpenID request is to be sent
  304.      *  to the server without any identifier information.  Use this
  305.      *  when you want to transport data but don't want to do OpenID
  306.      *  authentication with identifiers.
  307.      *
  308.      * @return Auth_OpenID_AuthRequest $auth_request An object
  309.      *  containing the discovered information will be returned, with a
  310.      *  method for building a redirect URL to the server, as described
  311.      *  in step 3 of the overview. This object may also be used to add
  312.      *  extension arguments to the request, using its 'addExtensionArg'
  313.      *  method.
  314.      */
  315.     function begin($user_url$anonymous=false)
  316.     {
  317.         $openid_url $user_url;
  318.  
  319.         $disco $this->getDiscoveryObject($this->session,
  320.                                            $openid_url,
  321.                                            $this->session_key_prefix);
  322.  
  323.         // Set the 'stale' attribute of the manager.  If discovery
  324.         // fails in a fatal way, the stale flag will cause the manager
  325.         // to be cleaned up next time discovery is attempted.
  326.  
  327.         $m $disco->getManager();
  328.         $loader new Auth_Yadis_ManagerLoader();
  329.  
  330.         if ($m{
  331.             if ($m->stale{
  332.                 $disco->destroyManager();
  333.             else {
  334.                 $m->stale true;
  335.                 $disco->session->set($disco->session_key,
  336.                                      serialize($loader->toSession($m)));
  337.             }
  338.         }
  339.  
  340.         $endpoint $disco->getNextService($this->discoverMethod,
  341.                                            $this->consumer->fetcher);
  342.  
  343.         // Reset the 'stale' attribute of the manager.
  344.         $m =$disco->getManager();
  345.         if ($m{
  346.             $m->stale false;
  347.             $disco->session->set($disco->session_key,
  348.                                  serialize($loader->toSession($m)));
  349.         }
  350.  
  351.         if ($endpoint === null{
  352.             return null;
  353.         else {
  354.             return $this->beginWithoutDiscovery($endpoint,
  355.                                                 $anonymous);
  356.         }
  357.     }
  358.  
  359.     /**
  360.      * Start OpenID verification without doing OpenID server
  361.      * discovery. This method is used internally by Consumer.begin
  362.      * after discovery is performed, and exists to provide an
  363.      * interface for library users needing to perform their own
  364.      * discovery.
  365.      *
  366.      * @param Auth_OpenID_ServiceEndpoint $endpoint an OpenID service
  367.      *  endpoint descriptor.
  368.      *
  369.      * @param bool anonymous Set to true if you want to perform OpenID
  370.      *  without identifiers.
  371.      *
  372.      * @return Auth_OpenID_AuthRequest $auth_request An OpenID
  373.      *  authentication request object.
  374.      */
  375.     function &beginWithoutDiscovery($endpoint$anonymous=false)
  376.     {
  377.         $loader new Auth_OpenID_ServiceEndpointLoader();
  378.         $auth_req $this->consumer->begin($endpoint);
  379.         $this->session->set($this->_token_key,
  380.               $loader->toSession($auth_req->endpoint));
  381.         if (!$auth_req->setAnonymous($anonymous)) {
  382.             return new Auth_OpenID_FailureResponse(null,
  383.               "OpenID 1 requests MUST include the identifier " .
  384.               "in the request.");
  385.         }
  386.         return $auth_req;
  387.     }
  388.  
  389.     /**
  390.      * Called to interpret the server's response to an OpenID
  391.      * request. It is called in step 4 of the flow described in the
  392.      * consumer overview.
  393.      *
  394.      * @param string $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.current_url value in the response.  If
  398.      *  the current_url URL check fails, the status of the
  399.      *  completion will be FAILURE.
  400.      *
  401.      * @param array $query An array of the query parameters (key =>
  402.      *  value pairs) for this HTTP request.  Defaults to null.  If
  403.      *  null, the GET or POST data are automatically gotten from the
  404.      *  PHP environment.  It is only useful to override $query for
  405.      *  testing.
  406.      *
  407.      * @return Auth_OpenID_ConsumerResponse $response A instance of an
  408.      *  Auth_OpenID_ConsumerResponse subclass. The type of response is
  409.      *  indicated by the status attribute, which will be one of
  410.      *  SUCCESS, CANCEL, FAILURE, or SETUP_NEEDED.
  411.      */
  412.     function complete($current_url$query=null)
  413.     {
  414.         if ($current_url && !is_string($current_url)) {
  415.             // This is ugly, but we need to complain loudly when
  416.             // someone uses the API incorrectly.
  417.             trigger_error("current_url must be a string; see NEWS file " .
  418.                           "for upgrading notes.",
  419.                           E_USER_ERROR);
  420.         }
  421.  
  422.         if ($query === null{
  423.             $query Auth_OpenID::getQuery();
  424.         }
  425.  
  426.         $loader new Auth_OpenID_ServiceEndpointLoader();
  427.         $endpoint_data $this->session->get($this->_token_key);
  428.         $endpoint =
  429.             $loader->fromSession($endpoint_data);
  430.  
  431.         $message Auth_OpenID_Message::fromPostArgs($query);
  432.         $response $this->consumer->complete($message$endpoint
  433.                                               $current_url);
  434.         $this->session->del($this->_token_key);
  435.  
  436.         if (in_array($response->statusarray(Auth_OpenID_SUCCESS,
  437.                                               Auth_OpenID_CANCEL))) {
  438.             if ($response->identity_url !== null{
  439.                 $disco $this->getDiscoveryObject($this->session,
  440.                                                    $response->identity_url,
  441.                                                    $this->session_key_prefix);
  442.                 $disco->cleanup(true);
  443.             }
  444.         }
  445.  
  446.         return $response;
  447.     }
  448. }
  449.  
  450. /**
  451.  * A class implementing HMAC/DH-SHA1 consumer sessions.
  452.  *
  453.  * @package OpenID
  454.  */
  455.     var $session_type = 'DH-SHA1';
  456.     var $hash_func = 'Auth_OpenID_SHA1';
  457.     var $secret_size = 20;
  458.     var $allowed_assoc_types = array('HMAC-SHA1');
  459.  
  460.     function Auth_OpenID_DiffieHellmanSHA1ConsumerSession($dh null)
  461.     {
  462.         if ($dh === null{
  463.             $dh new Auth_OpenID_DiffieHellman();
  464.         }
  465.  
  466.         $this->dh $dh;
  467.     }
  468.  
  469.     function getRequest()
  470.     {
  471.         $math =Auth_OpenID_getMathLib();
  472.  
  473.         $cpub $math->longToBase64($this->dh->public);
  474.  
  475.         $args array('dh_consumer_public' => $cpub);
  476.  
  477.         if (!$this->dh->usingDefaultValues()) {
  478.             $args array_merge($argsarray(
  479.                 'dh_modulus' =>
  480.                      $math->longToBase64($this->dh->mod),
  481.                 'dh_gen' =>
  482.                      $math->longToBase64($this->dh->gen)));
  483.         }
  484.  
  485.         return $args;
  486.     }
  487.  
  488.     function extractSecret($response)
  489.     {
  490.         if (!$response->hasKey(Auth_OpenID_OPENID_NS,
  491.                                'dh_server_public')) {
  492.             return null;
  493.         }
  494.  
  495.         if (!$response->hasKey(Auth_OpenID_OPENID_NS,
  496.                                'enc_mac_key')) {
  497.             return null;
  498.         }
  499.  
  500.         $math =Auth_OpenID_getMathLib();
  501.  
  502.         $spub $math->base64ToLong($response->getArg(Auth_OpenID_OPENID_NS,
  503.                                                       'dh_server_public'));
  504.         $enc_mac_key base64_decode($response->getArg(Auth_OpenID_OPENID_NS,
  505.                                                        'enc_mac_key'));
  506.  
  507.         return $this->dh->xorSecret($spub$enc_mac_key$this->hash_func);
  508.     }
  509. }
  510.  
  511. /**
  512.  * A class implementing HMAC/DH-SHA256 consumer sessions.
  513.  *
  514.  * @package OpenID
  515.  */
  516.     var $session_type = 'DH-SHA256';
  517.     var $hash_func = 'Auth_OpenID_SHA256';
  518.     var $secret_size = 32;
  519.     var $allowed_assoc_types = array('HMAC-SHA256');
  520. }
  521.  
  522. /**
  523.  * A class implementing plaintext consumer sessions.
  524.  *
  525.  * @package OpenID
  526.  */
  527.     var $session_type = 'no-encryption';
  528.     var $allowed_assoc_types =  array('HMAC-SHA1''HMAC-SHA256');
  529.  
  530.     function getRequest()
  531.     {
  532.         return array();
  533.     }
  534.  
  535.     function extractSecret($response)
  536.     {
  537.         if (!$response->hasKey(Auth_OpenID_OPENID_NS'mac_key')) {
  538.             return null;
  539.         }
  540.  
  541.         return base64_decode($response->getArg(Auth_OpenID_OPENID_NS,
  542.                                                'mac_key'));
  543.     }
  544. }
  545.  
  546. /**
  547.  * Returns available session types.
  548.  */
  549. {
  550.     $types array(
  551.       'no-encryption' => 'Auth_OpenID_PlainTextConsumerSession',
  552.       'DH-SHA1' => 'Auth_OpenID_DiffieHellmanSHA1ConsumerSession',
  553.       'DH-SHA256' => 'Auth_OpenID_DiffieHellmanSHA256ConsumerSession');
  554.  
  555.     return $types;
  556. }
  557.  
  558. /**
  559.  * This class is the interface to the OpenID consumer logic.
  560.  * Instances of it maintain no per-request state, so they can be
  561.  * reused (or even used by multiple threads concurrently) as needed.
  562.  *
  563.  * @package OpenID
  564.  */
  565.     /**
  566.      * @access private
  567.      */
  568.     var $discoverMethod 'Auth_OpenID_discover';
  569.  
  570.     /**
  571.      * This consumer's store object.
  572.      */
  573.     var $store;
  574.  
  575.     /**
  576.      * @access private
  577.      */
  578.     var $_use_assocs;
  579.  
  580.     /**
  581.      * @access private
  582.      */
  583.     var $openid1_nonce_query_arg_name 'janrain_nonce';
  584.  
  585.     /**
  586.      * Another query parameter that gets added to the return_to for
  587.      * OpenID 1; if the user's session state is lost, use this claimed
  588.      * identifier to do discovery when verifying the response.
  589.      */
  590.     var $openid1_return_to_identifier_name = 'openid1_claimed_id';
  591.  
  592.     /**
  593.      * This method initializes a new {@link Auth_OpenID_Consumer}
  594.      * instance to access the library.
  595.      *
  596.      * @param Auth_OpenID_OpenIDStore $store This must be an object
  597.      *  that implements the interface in {@link Auth_OpenID_OpenIDStore}.
  598.      *  Several concrete implementations are provided, to cover most common use
  599.      *  cases.  For stores backed by MySQL, PostgreSQL, or SQLite, see
  600.      *  the {@link Auth_OpenID_SQLStore} class and its sublcasses.  For a
  601.      *  filesystem-backed store, see the {@link Auth_OpenID_FileStore} module.
  602.      *  As a last resort, if it isn't possible for the server to store
  603.      *  state at all, an instance of {@link Auth_OpenID_DumbStore} can be used.
  604.      *
  605.      * @param bool $immediate This is an optional boolean value.  It
  606.      *  controls whether the library uses immediate mode, as explained
  607.      *  in the module description.  The default value is False, which
  608.      *  disables immediate mode.
  609.      */
  610.     function Auth_OpenID_GenericConsumer(&$store)
  611.     {
  612.         $this->store =$store;
  613.         $this->negotiator =Auth_OpenID_getDefaultNegotiator();
  614.         $this->_use_assocs ($this->store ? true false);
  615.  
  616.         $this->fetcher Auth_Yadis_Yadis::getHTTPFetcher();
  617.  
  618.         $this->session_types Auth_OpenID_getAvailableSessionTypes();
  619.     }
  620.  
  621.     /**
  622.      * Called to begin OpenID authentication using the specified
  623.      * {@link Auth_OpenID_ServiceEndpoint}.
  624.      *
  625.      * @access private
  626.      */
  627.     function begin($service_endpoint)
  628.     {
  629.         $assoc $this->_getAssociation($service_endpoint);
  630.         $r new Auth_OpenID_AuthRequest($service_endpoint$assoc);
  631.         $r->return_to_args[$this->openid1_nonce_query_arg_name=
  632.             Auth_OpenID_mkNonce();
  633.  
  634.         if ($r->message->isOpenID1()) {
  635.             $r->return_to_args[$this->openid1_return_to_identifier_name=
  636.                 $r->endpoint->claimed_id;
  637.         }
  638.  
  639.         return $r;
  640.     }
  641.  
  642.     /**
  643.      * Given an {@link Auth_OpenID_Message}{@link }
  644.      * Auth_OpenID_ServiceEndpoint} and optional return_to URL,
  645.      * complete OpenID authentication.
  646.      *
  647.      * @access private
  648.      */
  649.     function complete($message$endpoint$return_to)
  650.     {
  651.         $mode $message->getArg(Auth_OpenID_OPENID_NS'mode',
  652.                                  '<no mode set>');
  653.  
  654.         $mode_methods array(
  655.                               'cancel' => '_complete_cancel',
  656.                               'error' => '_complete_error',
  657.                               'setup_needed' => '_complete_setup_needed',
  658.                               'id_res' => '_complete_id_res',
  659.                               );
  660.  
  661.         $method Auth_OpenID::arrayGet($mode_methods$mode,
  662.                                         '_completeInvalid');
  663.  
  664.         return call_user_func_array(array(&$this$method),
  665.                                     array($message$endpoint$return_to));
  666.     }
  667.  
  668.     /**
  669.      * @access private
  670.      */
  671.     function _completeInvalid($message&$endpoint$unused)
  672.     {
  673.         $mode $message->getArg(Auth_OpenID_OPENID_NS'mode',
  674.                                  '<No mode set>');
  675.  
  676.         return new Auth_OpenID_FailureResponse($endpoint,
  677.                     sprintf("Invalid openid.mode '%s'"$mode));
  678.     }
  679.  
  680.     /**
  681.      * @access private
  682.      */
  683.     function _complete_cancel($message&$endpoint$unused)
  684.     {
  685.         return new Auth_OpenID_CancelResponse($endpoint);
  686.     }
  687.  
  688.     /**
  689.      * @access private
  690.      */
  691.     function _complete_error($message&$endpoint$unused)
  692.     {
  693.         $error $message->getArg(Auth_OpenID_OPENID_NS'error');
  694.         $contact $message->getArg(Auth_OpenID_OPENID_NS'contact');
  695.         $reference $message->getArg(Auth_OpenID_OPENID_NS'reference');
  696.  
  697.         return new Auth_OpenID_FailureResponse($endpoint$error,
  698.                                                $contact$reference);
  699.     }
  700.  
  701.     /**
  702.      * @access private
  703.      */
  704.     function _complete_setup_needed($message&$endpoint$unused)
  705.     {
  706.         if (!$message->isOpenID2()) {
  707.             return $this->_completeInvalid($message$endpoint);
  708.         }
  709.  
  710.         $user_setup_url $message->getArg(Auth_OpenID_OPENID2_NS,
  711.                                            'user_setup_url');
  712.         return new Auth_OpenID_SetupNeededResponse($endpoint$user_setup_url);
  713.     }
  714.  
  715.     /**
  716.      * @access private
  717.      */
  718.     function _complete_id_res($message&$endpoint$return_to)
  719.     {
  720.         $user_setup_url $message->getArg(Auth_OpenID_OPENID1_NS,
  721.                                            'user_setup_url');
  722.  
  723.         if ($this->_checkSetupNeeded($message)) {
  724.             return new Auth_OpenID_SetupNeededResponse(
  725.                 $endpoint$user_setup_url);
  726.         else {
  727.             return $this->_doIdRes($message$endpoint$return_to);
  728.         }
  729.     }
  730.  
  731.     /**
  732.      * @access private
  733.      */
  734.     function _checkSetupNeeded($message)
  735.     {
  736.         // In OpenID 1, we check to see if this is a cancel from
  737.         // immediate mode by the presence of the user_setup_url
  738.         // parameter.
  739.         if ($message->isOpenID1()) {
  740.             $user_setup_url $message->getArg(Auth_OpenID_OPENID1_NS,
  741.                                                'user_setup_url');
  742.             if ($user_setup_url !== null{
  743.                 return true;
  744.             }
  745.         }
  746.  
  747.         return false;
  748.     }
  749.  
  750.     /**
  751.      * @access private
  752.      */
  753.     function _doIdRes($message$endpoint$return_to)
  754.     {
  755.         // Checks for presence of appropriate fields (and checks
  756.         // signed list fields)
  757.         $result $this->_idResCheckForFields($message);
  758.  
  759.         if (Auth_OpenID::isFailure($result)) {
  760.             return $result;
  761.         }
  762.  
  763.         if (!$this->_checkReturnTo($message$return_to)) {
  764.             return new Auth_OpenID_FailureResponse(null,
  765.             sprintf("return_to does not match return URL. Expected %s, got %s",
  766.                     $return_to,
  767.                     $message->getArg(Auth_OpenID_OPENID_NS'return_to')));
  768.         }
  769.  
  770.         // Verify discovery information:
  771.         $result $this->_verifyDiscoveryResults($message$endpoint);
  772.  
  773.         if (Auth_OpenID::isFailure($result)) {
  774.             return $result;
  775.         }
  776.  
  777.         $endpoint $result;
  778.  
  779.         $result $this->_idResCheckSignature($message,
  780.                                               $endpoint->server_url);
  781.  
  782.         if (Auth_OpenID::isFailure($result)) {
  783.             return $result;
  784.         }
  785.  
  786.         $result $this->_idResCheckNonce($message$endpoint);
  787.  
  788.         if (Auth_OpenID::isFailure($result)) {
  789.             return $result;
  790.         }
  791.  
  792.         $signed_list_str $message->getArg(Auth_OpenID_OPENID_NS'signed',
  793.                                             Auth_OpenID_NO_DEFAULT);
  794.         if (Auth_OpenID::isFailure($signed_list_str)) {
  795.             return $signed_list_str;
  796.         }
  797.         $signed_list explode(','$signed_list_str);
  798.  
  799.         $signed_fields Auth_OpenID::addPrefix($signed_list"openid.");
  800.  
  801.         return new Auth_OpenID_SuccessResponse($endpoint$message,
  802.                                                $signed_fields);
  803.  
  804.     }
  805.  
  806.     /**
  807.      * @access private
  808.      */
  809.     function _checkReturnTo($message$return_to)
  810.     {
  811.         // Check an OpenID message and its openid.return_to value
  812.         // against a return_to URL from an application.  Return True
  813.         // on success, False on failure.
  814.  
  815.         // Check the openid.return_to args against args in the
  816.         // original message.
  817.         $result Auth_OpenID_GenericConsumer::_verifyReturnToArgs(
  818.                                            $message->toPostArgs());
  819.         if (Auth_OpenID::isFailure($result)) {
  820.             return false;
  821.         }
  822.  
  823.         // Check the return_to base URL against the one in the
  824.         // message.
  825.         $msg_return_to $message->getArg(Auth_OpenID_OPENID_NS,
  826.                                           'return_to');
  827.         if (Auth_OpenID::isFailure($return_to)) {
  828.             // XXX log me
  829.             return false;
  830.         }
  831.  
  832.         $return_to_parts parse_url(Auth_OpenID_urinorm($return_to));
  833.         $msg_return_to_parts parse_url(Auth_OpenID_urinorm($msg_return_to));
  834.  
  835.         // If port is absent from both, add it so it's equal in the
  836.         // check below.
  837.         if ((!array_key_exists('port'$return_to_parts)) &&
  838.             (!array_key_exists('port'$msg_return_to_parts))) {
  839.             $return_to_parts['port'null;
  840.             $msg_return_to_parts['port'null;
  841.         }
  842.  
  843.         // If path is absent from both, add it so it's equal in the
  844.         // check below.
  845.         if ((!array_key_exists('path'$return_to_parts)) &&
  846.             (!array_key_exists('path'$msg_return_to_parts))) {
  847.             $return_to_parts['path'null;
  848.             $msg_return_to_parts['path'null;
  849.         }
  850.  
  851.         // The URL scheme, authority, and path MUST be the same
  852.         // between the two URLs.
  853.         foreach (array('scheme''host''port''path'as $component{
  854.             // If the url component is absent in either URL, fail.
  855.             // There should always be a scheme, host, port, and path.
  856.             if (!array_key_exists($component$return_to_parts)) {
  857.                 return false;
  858.             }
  859.  
  860.             if (!array_key_exists($component$msg_return_to_parts)) {
  861.                 return false;
  862.             }
  863.  
  864.             if (Auth_OpenID::arrayGet($return_to_parts$component!==
  865.                 Auth_OpenID::arrayGet($msg_return_to_parts$component)) {
  866.                 return false;
  867.             }
  868.         }
  869.  
  870.         return true;
  871.     }
  872.  
  873.     /**
  874.      * @access private
  875.      */
  876.     function _verifyReturnToArgs($query)
  877.     {
  878.         // Verify that the arguments in the return_to URL are present in this
  879.         // response.
  880.  
  881.         $message Auth_OpenID_Message::fromPostArgs($query);
  882.         $return_to $message->getArg(Auth_OpenID_OPENID_NS'return_to');
  883.  
  884.         if (Auth_OpenID::isFailure($return_to)) {
  885.             return $return_to;
  886.         }
  887.         // XXX: this should be checked by _idResCheckForFields
  888.         if (!$return_to{
  889.             return new Auth_OpenID_FailureResponse(null,
  890.                            "Response has no return_to");
  891.         }
  892.  
  893.         $parsed_url parse_url($return_to);
  894.  
  895.         $q array();
  896.         if (array_key_exists('query'$parsed_url)) {
  897.             $rt_query $parsed_url['query'];
  898.             $q Auth_OpenID::parse_str($rt_query);
  899.         }
  900.  
  901.         foreach ($q as $rt_key => $rt_value{
  902.             if (!array_key_exists($rt_key$query)) {
  903.                 return new Auth_OpenID_FailureResponse(null,
  904.                   sprintf("return_to parameter %s absent from query"$rt_key));
  905.             else {
  906.                 $value $query[$rt_key];
  907.                 if ($rt_value != $value{
  908.                     return new Auth_OpenID_FailureResponse(null,
  909.                       sprintf("parameter %s value %s does not match " .
  910.                               "return_to value %s"$rt_key,
  911.                               $value$rt_value));
  912.                 }
  913.             }
  914.         }
  915.  
  916.         // Make sure all non-OpenID arguments in the response are also
  917.         // in the signed return_to.
  918.         $bare_args $message->getArgs(Auth_OpenID_BARE_NS);
  919.         foreach ($bare_args as $key => $value{
  920.             if (Auth_OpenID::arrayGet($q$key!= $value{
  921.                 return new Auth_OpenID_FailureResponse(null,
  922.                   sprintf("Parameter %s = %s not in return_to URL",
  923.                           $key$value));
  924.             }
  925.         }
  926.  
  927.         return true;
  928.     }
  929.  
  930.     /**
  931.      * @access private
  932.      */
  933.     function _idResCheckSignature($message$server_url)
  934.     {
  935.         $assoc_handle $message->getArg(Auth_OpenID_OPENID_NS,
  936.                                          'assoc_handle');
  937.         if (Auth_OpenID::isFailure($assoc_handle)) {
  938.             return $assoc_handle;
  939.         }
  940.  
  941.         $assoc $this->store->getAssociation($server_url$assoc_handle);
  942.  
  943.         if ($assoc{
  944.             if ($assoc->getExpiresIn(<= 0{
  945.                 // XXX: It might be a good idea sometimes to re-start
  946.                 // the authentication with a new association. Doing it
  947.                 // automatically opens the possibility for
  948.                 // denial-of-service by a server that just returns
  949.                 // expired associations (or really short-lived
  950.                 // associations)
  951.                 return new Auth_OpenID_FailureResponse(null,
  952.                              'Association with ' $server_url ' expired');
  953.             }
  954.  
  955.             if (!$assoc->checkMessageSignature($message)) {
  956.                 return new Auth_OpenID_FailureResponse(null,
  957.                                                        "Bad signature");
  958.             }
  959.         else {
  960.             // It's not an association we know about.  Stateless mode
  961.             // is our only possible path for recovery.  XXX - async
  962.             // framework will not want to block on this call to
  963.             // _checkAuth.
  964.             if (!$this->_checkAuth($message$server_url)) {
  965.                 return new Auth_OpenID_FailureResponse(null,
  966.                              "Server denied check_authentication");
  967.             }
  968.         }
  969.  
  970.         return null;
  971.     }
  972.  
  973.     /**
  974.      * @access private
  975.      */
  976.     function _verifyDiscoveryResults($message$endpoint=null)
  977.     {
  978.         if ($message->getOpenIDNamespace(== Auth_OpenID_OPENID2_NS{
  979.             return $this->_verifyDiscoveryResultsOpenID2($message,
  980.                                                          $endpoint);
  981.         else {
  982.             return $this->_verifyDiscoveryResultsOpenID1($message,
  983.                                                          $endpoint);
  984.         }
  985.     }
  986.  
  987.     /**
  988.      * @access private
  989.      */
  990.     function _verifyDiscoveryResultsOpenID1($message$endpoint)
  991.     {
  992.         $claimed_id $message->getArg(Auth_OpenID_BARE_NS,
  993.                                 $this->openid1_return_to_identifier_name);
  994.  
  995.         if (($endpoint === null&& ($claimed_id === null)) {
  996.             return new Auth_OpenID_FailureResponse($endpoint,
  997.               'When using OpenID 1, the claimed ID must be supplied, ' .
  998.               'either by passing it through as a return_to parameter ' .
  999.               'or by using a session, and supplied to the GenericConsumer ' .
  1000.               'as the argument to complete()');
  1001.         else if (($endpoint !== null&& ($claimed_id === null)) {
  1002.             $claimed_id $endpoint->claimed_id;
  1003.         }
  1004.  
  1005.         $to_match new Auth_OpenID_ServiceEndpoint();
  1006.         $to_match->type_uris array(Auth_OpenID_TYPE_1_1);
  1007.         $to_match->local_id $message->getArg(Auth_OpenID_OPENID1_NS,
  1008.                                                'identity');
  1009.  
  1010.         // Restore delegate information from the initiation phase
  1011.         $to_match->claimed_id $claimed_id;
  1012.  
  1013.         if ($to_match->local_id === null{
  1014.             return new Auth_OpenID_FailureResponse($endpoint,
  1015.                          "Missing required field openid.identity");
  1016.         }
  1017.  
  1018.         $to_match_1_0 $to_match->copy();
  1019.         $to_match_1_0->type_uris array(Auth_OpenID_TYPE_1_0);
  1020.  
  1021.         if ($endpoint !== null{
  1022.             $result $this->_verifyDiscoverySingle($endpoint$to_match);
  1023.  
  1024.             if (is_a($result'Auth_OpenID_TypeURIMismatch')) {
  1025.                 $result $this->_verifyDiscoverySingle($endpoint,
  1026.                                                         $to_match_1_0);
  1027.             }
  1028.  
  1029.             if (Auth_OpenID::isFailure($result)) {
  1030.                 // oidutil.log("Error attempting to use stored
  1031.                 //             discovery information: " + str(e))
  1032.                 //             oidutil.log("Attempting discovery to
  1033.                 //             verify endpoint")
  1034.             else {
  1035.                 return $endpoint;
  1036.             }
  1037.         }
  1038.  
  1039.         // Endpoint is either bad (failed verification) or None
  1040.         return $this->_discoverAndVerify($to_match->claimed_id,
  1041.                                          array($to_match$to_match_1_0));
  1042.     }
  1043.  
  1044.     /**
  1045.      * @access private
  1046.      */
  1047.     function _verifyDiscoverySingle($endpoint$to_match)
  1048.     {
  1049.         // Every type URI that's in the to_match endpoint has to be
  1050.         // present in the discovered endpoint.
  1051.         foreach ($to_match->type_uris as $type_uri{
  1052.             if (!$endpoint->usesExtension($type_uri)) {
  1053.                 return new Auth_OpenID_TypeURIMismatch($endpoint,
  1054.                              "Required type ".$type_uri." not present");
  1055.             }
  1056.         }
  1057.  
  1058.         // Fragments do not influence discovery, so we can't compare a
  1059.         // claimed identifier with a fragment to discovered
  1060.         // information.
  1061.         list($defragged_claimed_id$_=
  1062.             Auth_OpenID::urldefrag($to_match->claimed_id);
  1063.  
  1064.         if ($defragged_claimed_id != $endpoint->claimed_id{
  1065.             return new Auth_OpenID_FailureResponse($endpoint,
  1066.               sprintf('Claimed ID does not match (different subjects!), ' .
  1067.                       'Expected %s, got %s'$defragged_claimed_id,
  1068.                       $endpoint->claimed_id));
  1069.         }
  1070.  
  1071.         if ($to_match->getLocalID(!= $endpoint->getLocalID()) {
  1072.             return new Auth_OpenID_FailureResponse($endpoint,
  1073.               sprintf('local_id mismatch. Expected %s, got %s',
  1074.                       $to_match->getLocalID()$endpoint->getLocalID()));
  1075.         }
  1076.  
  1077.         // If the server URL is None, this must be an OpenID 1
  1078.         // response, because op_endpoint is a required parameter in
  1079.         // OpenID 2. In that case, we don't actually care what the
  1080.         // discovered server_url is, because signature checking or
  1081.         // check_auth should take care of that check for us.
  1082.         if ($to_match->server_url === null{
  1083.             if ($to_match->preferredNamespace(!= Auth_OpenID_OPENID1_NS{
  1084.                 return new Auth_OpenID_FailureResponse($endpoint,
  1085.                              "Preferred namespace mismatch (bug)");
  1086.             }
  1087.         else if ($to_match->server_url != $endpoint->server_url{
  1088.             return new Auth_OpenID_FailureResponse($endpoint,
  1089.               sprintf('OP Endpoint mismatch. Expected %s, got %s',
  1090.                       $to_match->server_url$endpoint->server_url));
  1091.         }
  1092.  
  1093.         return null;
  1094.     }
  1095.  
  1096.     /**
  1097.      * @access private
  1098.      */
  1099.     function _verifyDiscoveryResultsOpenID2($message$endpoint)
  1100.     {
  1101.         $to_match new Auth_OpenID_ServiceEndpoint();
  1102.         $to_match->type_uris array(Auth_OpenID_TYPE_2_0);
  1103.         $to_match->claimed_id $message->getArg(Auth_OpenID_OPENID2_NS,
  1104.                                                  'claimed_id');
  1105.  
  1106.         $to_match->local_id $message->getArg(Auth_OpenID_OPENID2_NS,
  1107.                                                 'identity');
  1108.  
  1109.         $to_match->server_url $message->getArg(Auth_OpenID_OPENID2_NS,
  1110.                                                  'op_endpoint');
  1111.  
  1112.         if ($to_match->server_url === null{
  1113.             return new Auth_OpenID_FailureResponse($endpoint,
  1114.                          "OP Endpoint URL missing");
  1115.         }
  1116.  
  1117.         // claimed_id and identifier must both be present or both be
  1118.         // absent
  1119.         if (($to_match->claimed_id === null&&
  1120.             ($to_match->local_id !== null)) {
  1121.             return new Auth_OpenID_FailureResponse($endpoint,
  1122.               'openid.identity is present without openid.claimed_id');
  1123.         }
  1124.  
  1125.         if (($to_match->claimed_id !== null&&
  1126.             ($to_match->local_id === null)) {
  1127.             return new Auth_OpenID_FailureResponse($endpoint,
  1128.               'openid.claimed_id is present without openid.identity');
  1129.         }
  1130.  
  1131.         if ($to_match->claimed_id === null{
  1132.             // This is a response without identifiers, so there's
  1133.             // really no checking that we can do, so return an
  1134.             // endpoint that's for the specified `openid.op_endpoint'
  1135.             return Auth_OpenID_ServiceEndpoint::fromOPEndpointURL(
  1136.                                                 $to_match->server_url);
  1137.         }
  1138.  
  1139.         if (!$endpoint{
  1140.             // The claimed ID doesn't match, so we have to do
  1141.             // discovery again. This covers not using sessions, OP
  1142.             // identifier endpoints and responses that didn't match
  1143.             // the original request.
  1144.             // oidutil.log('No pre-discovered information supplied.')
  1145.             return $this->_discoverAndVerify($to_match->claimed_id,
  1146.                                              array($to_match));
  1147.         else {
  1148.  
  1149.             // The claimed ID matches, so we use the endpoint that we
  1150.             // discovered in initiation. This should be the most
  1151.             // common case.
  1152.             $result $this->_verifyDiscoverySingle($endpoint$to_match);
  1153.  
  1154.             if (Auth_OpenID::isFailure($result)) {
  1155.                 $endpoint $this->_discoverAndVerify($to_match->claimed_id,
  1156.                                                       array($to_match));
  1157.                 if (Auth_OpenID::isFailure($endpoint)) {
  1158.                     return $endpoint;
  1159.                 }
  1160.             }
  1161.         }
  1162.  
  1163.         // The endpoint we return should have the claimed ID from the
  1164.         // message we just verified, fragment and all.
  1165.         if ($endpoint->claimed_id != $to_match->claimed_id{
  1166.             $endpoint->claimed_id $to_match->claimed_id;
  1167.         }
  1168.  
  1169.         return $endpoint;
  1170.     }
  1171.  
  1172.     /**
  1173.      * @access private
  1174.      */
  1175.     function _discoverAndVerify($claimed_id$to_match_endpoints)
  1176.     {
  1177.         // oidutil.log('Performing discovery on %s' % (claimed_id,))
  1178.         list($unused$servicescall_user_func($this->discoverMethod,
  1179.                                                   $claimed_id,
  1180.                                                   $this->fetcher);
  1181.  
  1182.         if (!$services{
  1183.             return new Auth_OpenID_FailureResponse(null,
  1184.               sprintf("No OpenID information found at %s",
  1185.                       $claimed_id));
  1186.         }
  1187.  
  1188.         return $this->_verifyDiscoveryServices($claimed_id$services,
  1189.                                                $to_match_endpoints);
  1190.     }
  1191.  
  1192.     /**
  1193.      * @access private
  1194.      */
  1195.     function _verifyDiscoveryServices($claimed_id
  1196.                                       &$services&$to_match_endpoints)
  1197.     {
  1198.         // Search the services resulting from discovery to find one
  1199.         // that matches the information from the assertion
  1200.  
  1201.         foreach ($services as $endpoint{
  1202.             foreach ($to_match_endpoints as $to_match_endpoint{
  1203.                 $result $this->_verifyDiscoverySingle($endpoint
  1204.                                                         $to_match_endpoint);
  1205.  
  1206.                 if (!Auth_OpenID::isFailure($result)) {
  1207.                     // It matches, so discover verification has
  1208.                     // succeeded. Return this endpoint.
  1209.                     return $endpoint;
  1210.                 }
  1211.             }
  1212.         }
  1213.  
  1214.         return new Auth_OpenID_FailureResponse(null,
  1215.           sprintf('No matching endpoint found after discovering %s',
  1216.                   $claimed_id));
  1217.     }
  1218.  
  1219.     /**
  1220.      * Extract the nonce from an OpenID 1 response.  Return the nonce
  1221.      * from the BARE_NS since we independently check the return_to
  1222.      * arguments are the same as those in the response message.
  1223.      *
  1224.      * See the openid1_nonce_query_arg_name class variable
  1225.      *
  1226.      * @returns $nonce The nonce as a string or null
  1227.      *
  1228.      * @access private
  1229.      */
  1230.     function _idResGetNonceOpenID1($message$endpoint)
  1231.     {
  1232.         return $message->getArg(Auth_OpenID_BARE_NS,
  1233.                                 $this->openid1_nonce_query_arg_name);
  1234.     }
  1235.  
  1236.     /**
  1237.      * @access private
  1238.      */
  1239.     function _idResCheckNonce($message$endpoint)
  1240.     {
  1241.         if ($message->isOpenID1()) {
  1242.             // This indicates that the nonce was generated by the consumer
  1243.             $nonce $this->_idResGetNonceOpenID1($message$endpoint);
  1244.             $server_url '';
  1245.         else {
  1246.             $nonce $message->getArg(Auth_OpenID_OPENID2_NS,
  1247.                                       'response_nonce');
  1248.  
  1249.             $server_url $endpoint->server_url;
  1250.         }
  1251.  
  1252.         if ($nonce === null{
  1253.             return new Auth_OpenID_FailureResponse($endpoint,
  1254.                                      "Nonce missing from response");
  1255.         }
  1256.  
  1257.         $parts Auth_OpenID_splitNonce($nonce);
  1258.  
  1259.         if ($parts === null{
  1260.             return new Auth_OpenID_FailureResponse($endpoint,
  1261.                                      "Malformed nonce in response");
  1262.         }
  1263.  
  1264.         list($timestamp$salt$parts;
  1265.  
  1266.         if (!$this->store->useNonce($server_url$timestamp$salt)) {
  1267.             return new Auth_OpenID_FailureResponse($endpoint,
  1268.                          "Nonce already used or out of range");
  1269.         }
  1270.  
  1271.         return null;
  1272.     }
  1273.  
  1274.     /**
  1275.      * @access private
  1276.      */
  1277.     function _idResCheckForFields($message)
  1278.     {
  1279.         $basic_fields array('return_to''assoc_handle''sig''signed');
  1280.         $basic_sig_fields array('return_to''identity');
  1281.  
  1282.         $require_fields array(
  1283.             Auth_OpenID_OPENID2_NS => array_merge($basic_fields,
  1284.                                                   array('op_endpoint')),
  1285.  
  1286.             Auth_OpenID_OPENID1_NS => array_merge($basic_fields,
  1287.                                                   array('identity'))
  1288.             );
  1289.  
  1290.         $require_sigs array(
  1291.             Auth_OpenID_OPENID2_NS => array_merge($basic_sig_fields,
  1292.                                                   array('response_nonce',
  1293.                                                         'claimed_id',
  1294.                                                         'assoc_handle')),
  1295.             Auth_OpenID_OPENID1_NS => array_merge($basic_sig_fields,
  1296.                                                   array('nonce'))
  1297.             );
  1298.  
  1299.         foreach ($require_fields[$message->getOpenIDNamespace()as $field{
  1300.             if (!$message->hasKey(Auth_OpenID_OPENID_NS$field)) {
  1301.                 return new Auth_OpenID_FailureResponse(null,
  1302.                              "Missing required field '".$field."'");
  1303.             }
  1304.         }
  1305.  
  1306.         $signed_list_str $message->getArg(Auth_OpenID_OPENID_NS,
  1307.                                             'signed',
  1308.                                             Auth_OpenID_NO_DEFAULT);
  1309.         if (Auth_OpenID::isFailure($signed_list_str)) {
  1310.             return $signed_list_str;
  1311.         }
  1312.         $signed_list explode(','$signed_list_str);
  1313.  
  1314.         foreach ($require_sigs[$message->getOpenIDNamespace()as $field{
  1315.             // Field is present and not in signed list
  1316.             if ($message->hasKey(Auth_OpenID_OPENID_NS$field&&
  1317.                 (!in_array($field$signed_list))) {
  1318.                 return new Auth_OpenID_FailureResponse(null,
  1319.                              "'".$field."' not signed");
  1320.             }
  1321.         }
  1322.  
  1323.         return null;
  1324.     }
  1325.  
  1326.     /**
  1327.      * @access private
  1328.      */
  1329.     function _checkAuth($message$server_url)
  1330.     {
  1331.         $request $this->_createCheckAuthRequest($message);
  1332.         if ($request === null{
  1333.             return false;
  1334.         }
  1335.  
  1336.         $resp_message $this->_makeKVPost($request$server_url);
  1337.         if (($resp_message === null||
  1338.             (is_a($resp_message'Auth_OpenID_ServerErrorContainer'))) {
  1339.             return false;
  1340.         }
  1341.  
  1342.         return $this->_processCheckAuthResponse($resp_message$server_url);
  1343.     }
  1344.  
  1345.     /**
  1346.      * @access private
  1347.      */
  1348.     function _createCheckAuthRequest($message)
  1349.     {
  1350.         $signed $message->getArg(Auth_OpenID_OPENID_NS'signed');
  1351.         if ($signed{
  1352.             foreach (explode(','$signedas $k{
  1353.                 $value $message->getAliasedArg($k);
  1354.                 if ($value === null{
  1355.                     return null;
  1356.                 }
  1357.             }
  1358.         }
  1359.         $ca_message $message->copy();
  1360.         $ca_message->setArg(Auth_OpenID_OPENID_NS'mode'
  1361.                             'check_authentication');
  1362.         return $ca_message;
  1363.     }
  1364.  
  1365.     /**
  1366.      * @access private
  1367.      */
  1368.     function _processCheckAuthResponse($response$server_url)
  1369.     {
  1370.         $is_valid $response->getArg(Auth_OpenID_OPENID_NS'is_valid',
  1371.                                       'false');
  1372.  
  1373.         $invalidate_handle $response->getArg(Auth_OpenID_OPENID_NS,
  1374.                                                'invalidate_handle');
  1375.  
  1376.         if ($invalidate_handle !== null{
  1377.             $this->store->removeAssociation($server_url,
  1378.                                             $invalidate_handle);
  1379.         }
  1380.  
  1381.         if ($is_valid == 'true'{
  1382.             return true;
  1383.         }
  1384.  
  1385.         return false;
  1386.     }
  1387.  
  1388.     /**
  1389.      * Adapt a POST response to a Message.
  1390.      *
  1391.      * @param $response Result of a POST to an OpenID endpoint.
  1392.      *
  1393.      * @access private
  1394.      */
  1395.     function _httpResponseToMessage($response$server_url)
  1396.     {
  1397.         // Should this function be named Message.fromHTTPResponse instead?
  1398.         $response_message Auth_OpenID_Message::fromKVForm($response->body);
  1399.  
  1400.         if ($response->status == 400{
  1401.             return Auth_OpenID_ServerErrorContainer::fromMessage(
  1402.                         $response_message);
  1403.         else if ($response->status != 200 and $response->status != 206{
  1404.             return null;
  1405.         }
  1406.  
  1407.         return $response_message;
  1408.     }
  1409.  
  1410.     /**
  1411.      * @access private
  1412.      */
  1413.     function _makeKVPost($message$server_url)
  1414.     {
  1415.         $body $message->toURLEncoded();
  1416.         $resp $this->fetcher->post($server_url$body);
  1417.  
  1418.         if ($resp === null{
  1419.             return null;
  1420.         }
  1421.  
  1422.         return $this->_httpResponseToMessage($resp$server_url);
  1423.     }
  1424.  
  1425.     /**
  1426.      * @access private
  1427.      */
  1428.     function _getAssociation($endpoint)
  1429.     {
  1430.         if (!$this->_use_assocs{
  1431.             return null;
  1432.         }
  1433.  
  1434.         $assoc $this->store->getAssociation($endpoint->server_url);
  1435.  
  1436.         if (($assoc === null||
  1437.             ($assoc->getExpiresIn(<= 0)) {
  1438.  
  1439.             $assoc $this->_negotiateAssociation($endpoint);
  1440.  
  1441.             if ($assoc !== null{
  1442.                 $this->store->storeAssociation($endpoint->server_url,
  1443.                                                $assoc);
  1444.             }
  1445.         }
  1446.  
  1447.         return $assoc;
  1448.     }
  1449.  
  1450.     /**
  1451.      * Handle ServerErrors resulting from association requests.
  1452.      *
  1453.      * @return $result If server replied with an C{unsupported-type}
  1454.      *  error, return a tuple of supported C{association_type},
  1455.      *  C{session_type}.  Otherwise logs the error and returns null.
  1456.      *
  1457.      * @access private
  1458.      */
  1459.     function _extractSupportedAssociationType(&$server_error&$endpoint,
  1460.                                               $assoc_type)
  1461.     {
  1462.         // Any error message whose code is not 'unsupported-type'
  1463.         // should be considered a total failure.
  1464.         if (($server_error->error_code != 'unsupported-type'||
  1465.             ($server_error->message->isOpenID1())) {
  1466.             return null;
  1467.         }
  1468.  
  1469.         // The server didn't like the association/session type that we
  1470.         // sent, and it sent us back a message that might tell us how
  1471.         // to handle it.
  1472.  
  1473.         // Extract the session_type and assoc_type from the error
  1474.         // message
  1475.         $assoc_type $server_error->message->getArg(Auth_OpenID_OPENID_NS,
  1476.                                                      'assoc_type');
  1477.  
  1478.         $session_type $server_error->message->getArg(Auth_OpenID_OPENID_NS,
  1479.                                                        'session_type');
  1480.  
  1481.         if (($assoc_type === null|| ($session_type === null)) {
  1482.             return null;
  1483.         else if (!$this->negotiator->isAllowed($assoc_type,
  1484.                                                  $session_type)) {
  1485.             return null;
  1486.         else {
  1487.           return array($assoc_type$session_type);
  1488.         }
  1489.     }
  1490.  
  1491.     /**
  1492.      * @access private
  1493.      */
  1494.     function _negotiateAssociation($endpoint)
  1495.     {
  1496.         // Get our preferred session/association type from the negotiatior.
  1497.         list($assoc_type$session_type$this->negotiator->getAllowedType();
  1498.  
  1499.         $assoc $this->_requestAssociation(
  1500.                            $endpoint$assoc_type$session_type);
  1501.  
  1502.         if (Auth_OpenID::isFailure($assoc)) {
  1503.             return null;
  1504.         }
  1505.  
  1506.         if (is_a($assoc'Auth_OpenID_ServerErrorContainer')) {
  1507.             $why $assoc;
  1508.  
  1509.             $supportedTypes $this->_extractSupportedAssociationType(
  1510.                                      $why$endpoint$assoc_type);
  1511.  
  1512.             if ($supportedTypes !== null{
  1513.                 list($assoc_type$session_type$supportedTypes;
  1514.  
  1515.                 // Attempt to create an association from the assoc_type
  1516.                 // and session_type that the server told us it
  1517.                 // supported.
  1518.                 $assoc $this->_requestAssociation(
  1519.                                    $endpoint$assoc_type$session_type);
  1520.  
  1521.                 if (is_a($assoc'Auth_OpenID_ServerErrorContainer')) {
  1522.                     // Do not keep trying, since it rejected the
  1523.                     // association type that it told us to use.
  1524.                     // oidutil.log('Server %s refused its suggested association
  1525.                     //             'type: session_type=%s, assoc_type=%s'
  1526.                     //             % (endpoint.server_url, session_type,
  1527.                     //                assoc_type))
  1528.                     return null;
  1529.                 else {
  1530.                     return $assoc;
  1531.                 }
  1532.             else {
  1533.                 return null;
  1534.             }
  1535.         else {
  1536.             return $assoc;
  1537.         }
  1538.     }
  1539.  
  1540.     /**
  1541.      * @access private
  1542.      */
  1543.     function _requestAssociation($endpoint$assoc_type$session_type)
  1544.     {
  1545.         list($assoc_session$args$this->_createAssociateRequest(
  1546.                                       $endpoint$assoc_type$session_type);
  1547.  
  1548.         $response_message $this->_makeKVPost($args$endpoint->server_url);
  1549.  
  1550.         if ($response_message === null{
  1551.             // oidutil.log('openid.associate request failed: %s' % (why[0],))
  1552.             return null;
  1553.         else if (is_a($response_message,
  1554.                         'Auth_OpenID_ServerErrorContainer')) {
  1555.             return $response_message;
  1556.         }
  1557.  
  1558.         return $this->_extractAssociation($response_message$assoc_session);
  1559.     }
  1560.  
  1561.     /**
  1562.      * @access private
  1563.      */
  1564.     function _extractAssociation(&$assoc_response&$assoc_session)
  1565.     {
  1566.         // Extract the common fields from the response, raising an
  1567.         // exception if they are not found
  1568.         $assoc_type $assoc_response->getArg(
  1569.                          Auth_OpenID_OPENID_NS'assoc_type',
  1570.                          Auth_OpenID_NO_DEFAULT);
  1571.  
  1572.         if (Auth_OpenID::isFailure($assoc_type)) {
  1573.             return $assoc_type;
  1574.         }
  1575.  
  1576.         $assoc_handle $assoc_response->getArg(
  1577.                            Auth_OpenID_OPENID_NS'assoc_handle',
  1578.                            Auth_OpenID_NO_DEFAULT);
  1579.  
  1580.         if (Auth_OpenID::isFailure($assoc_handle)) {
  1581.             return $assoc_handle;
  1582.         }
  1583.  
  1584.         // expires_in is a base-10 string. The Python parsing will
  1585.         // accept literals that have whitespace around them and will
  1586.         // accept negative values. Neither of these are really in-spec,
  1587.         // but we think it's OK to accept them.
  1588.         $expires_in_str $assoc_response->getArg(
  1589.                              Auth_OpenID_OPENID_NS'expires_in',
  1590.                              Auth_OpenID_NO_DEFAULT);
  1591.  
  1592.         if (Auth_OpenID::isFailure($expires_in_str)) {
  1593.             return $expires_in_str;
  1594.         }
  1595.  
  1596.         $expires_in Auth_OpenID::intval($expires_in_str);
  1597.         if ($expires_in === false{
  1598.             
  1599.             $err sprintf("Could not parse expires_in from association ".
  1600.                            "response %s"print_r($assoc_responsetrue));
  1601.             return new Auth_OpenID_FailureResponse(null$err);
  1602.         }
  1603.  
  1604.         // OpenID 1 has funny association session behaviour.
  1605.         if ($assoc_response->isOpenID1()) {
  1606.             $session_type $this->_getOpenID1SessionType($assoc_response);
  1607.         else {
  1608.             $session_type $assoc_response->getArg(
  1609.                                Auth_OpenID_OPENID2_NS'session_type',
  1610.                                Auth_OpenID_NO_DEFAULT);
  1611.  
  1612.             if (Auth_OpenID::isFailure($session_type)) {
  1613.                 return $session_type;
  1614.             }
  1615.         }
  1616.  
  1617.         // Session type mismatch
  1618.         if ($assoc_session->session_type != $session_type{
  1619.             if ($assoc_response->isOpenID1(&&
  1620.                 ($session_type == 'no-encryption')) {
  1621.                 // In OpenID 1, any association request can result in
  1622.                 // a 'no-encryption' association response. Setting
  1623.                 // assoc_session to a new no-encryption session should
  1624.                 // make the rest of this function work properly for
  1625.                 // that case.
  1626.                 $assoc_session new Auth_OpenID_PlainTextConsumerSession();
  1627.             else {
  1628.                 // Any other mismatch, regardless of protocol version
  1629.                 // results in the failure of the association session
  1630.                 // altogether.
  1631.                 return null;
  1632.             }
  1633.         }
  1634.  
  1635.         // Make sure assoc_type is valid for session_type
  1636.         if (!in_array($assoc_type$assoc_session->allowed_assoc_types)) {
  1637.             return null;
  1638.         }
  1639.  
  1640.         // Delegate to the association session to extract the secret
  1641.         // from the response, however is appropriate for that session
  1642.         // type.
  1643.         $secret $assoc_session->extractSecret($assoc_response);
  1644.  
  1645.         if ($secret === null{
  1646.             return null;
  1647.         }
  1648.  
  1649.         return Auth_OpenID_Association::fromExpiresIn(
  1650.                  $expires_in$assoc_handle$secret$assoc_type);
  1651.     }
  1652.  
  1653.     /**
  1654.      * @access private
  1655.      */
  1656.     function _createAssociateRequest($endpoint$assoc_type$session_type)
  1657.     {
  1658.         if (array_key_exists($session_type$this->session_types)) {
  1659.             $session_type_class $this->session_types[$session_type];
  1660.  
  1661.             if (is_callable($session_type_class)) {
  1662.                 $assoc_session $session_type_class();
  1663.             else {
  1664.                 $assoc_session new $session_type_class();
  1665.             }
  1666.         else {
  1667.             return null;
  1668.         }
  1669.  
  1670.         $args array(
  1671.             'mode' => 'associate',
  1672.             'assoc_type' => $assoc_type);
  1673.  
  1674.         if (!$endpoint->compatibilityMode()) {
  1675.             $args['ns'Auth_OpenID_OPENID2_NS;
  1676.         }
  1677.  
  1678.         // Leave out the session type if we're in compatibility mode
  1679.         // *and* it's no-encryption.
  1680.         if ((!$endpoint->compatibilityMode()) ||
  1681.             ($assoc_session->session_type != 'no-encryption')) {
  1682.             $args['session_type'$assoc_session->session_type;
  1683.         }
  1684.  
  1685.         $args array_merge($args$assoc_session->getRequest());
  1686.         $message Auth_OpenID_Message::fromOpenIDArgs($args);
  1687.         return array($assoc_session$message);
  1688.     }
  1689.  
  1690.     /**
  1691.      * Given an association response message, extract the OpenID 1.X
  1692.      * session type.
  1693.      *
  1694.      * This function mostly takes care of the 'no-encryption' default
  1695.      * behavior in OpenID 1.
  1696.      *
  1697.      * If the association type is plain-text, this function will
  1698.      * return 'no-encryption'
  1699.      *
  1700.      * @access private
  1701.      * @return $typ The association type for this message
  1702.      */
  1703.     function _getOpenID1SessionType($assoc_response)
  1704.     {
  1705.         // If it's an OpenID 1 message, allow session_type to default
  1706.         // to None (which signifies "no-encryption")
  1707.         $session_type $assoc_response->getArg(Auth_OpenID_OPENID1_NS,
  1708.                                                 'session_type');
  1709.  
  1710.         // Handle the differences between no-encryption association
  1711.         // respones in OpenID 1 and 2:
  1712.  
  1713.         // no-encryption is not really a valid session type for OpenID
  1714.         // 1, but we'll accept it anyway, while issuing a warning.
  1715.         if ($session_type == 'no-encryption'{
  1716.             // oidutil.log('WARNING: OpenID server sent "no-encryption"'
  1717.             //             'for OpenID 1.X')
  1718.         else if (($session_type == ''|| ($session_type === null)) {
  1719.             // Missing or empty session type is the way to flag a
  1720.             // 'no-encryption' response. Change the session type to
  1721.             // 'no-encryption' so that it can be handled in the same
  1722.             // way as OpenID 2 'no-encryption' respones.
  1723.             $session_type 'no-encryption';
  1724.         }
  1725.  
  1726.         return $session_type;
  1727.     }
  1728. }
  1729.  
  1730. /**
  1731.  * This class represents an authentication request from a consumer to
  1732.  * an OpenID server.
  1733.  *
  1734.  * @package OpenID
  1735.  */
  1736.  
  1737.     /**
  1738.      * Initialize an authentication request with the specified token,
  1739.      * association, and endpoint.
  1740.      *
  1741.      * Users of this library should not create instances of this
  1742.      * class.  Instances of this class are created by the library when
  1743.      * needed.
  1744.      */
  1745.     function Auth_OpenID_AuthRequest(&$endpoint$assoc)
  1746.     {
  1747.         $this->assoc $assoc;
  1748.         $this->endpoint =$endpoint;
  1749.         $this->return_to_args array();
  1750.         $this->message new Auth_OpenID_Message(
  1751.             $endpoint->preferredNamespace());
  1752.         $this->_anonymous false;
  1753.     }
  1754.  
  1755.     /**
  1756.      * Add an extension to this checkid request.
  1757.      *
  1758.      * $extension_request: An object that implements the extension
  1759.      * request interface for adding arguments to an OpenID message.
  1760.      */
  1761.     function addExtension(&$extension_request)
  1762.     {
  1763.         $extension_request->toMessage($this->message);
  1764.     }
  1765.  
  1766.     /**
  1767.      * Add an extension argument to this OpenID authentication
  1768.      * request.
  1769.      *
  1770.      * Use caution when adding arguments, because they will be
  1771.      * URL-escaped and appended to the redirect URL, which can easily
  1772.      * get quite long.
  1773.      *
  1774.      * @param string $namespace The namespace for the extension. For
  1775.      *  example, the simple registration extension uses the namespace
  1776.      *  'sreg'.
  1777.      *
  1778.      * @param string $key The key within the extension namespace. For
  1779.      *  example, the nickname field in the simple registration
  1780.      *  extension's key is 'nickname'.
  1781.      *
  1782.      * @param string $value The value to provide to the server for
  1783.      *  this argument.
  1784.      */
  1785.     function addExtensionArg($namespace$key$value)
  1786.     {
  1787.         return $this->message->setArg($namespace$key$value);
  1788.     }
  1789.  
  1790.     /**
  1791.      * Set whether this request should be made anonymously. If a
  1792.      * request is anonymous, the identifier will not be sent in the
  1793.      * request. This is only useful if you are making another kind of
  1794.      * request with an extension in this request.
  1795.      *
  1796.      * Anonymous requests are not allowed when the request is made
  1797.      * with OpenID 1.
  1798.      */
  1799.     function setAnonymous($is_anonymous)
  1800.     {
  1801.         if ($is_anonymous && $this->message->isOpenID1()) {
  1802.             return false;
  1803.         else {
  1804.             $this->_anonymous $is_anonymous;
  1805.             return true;
  1806.         }
  1807.     }
  1808.  
  1809.     /**
  1810.      * Produce a {@link Auth_OpenID_Message} representing this
  1811.      * request.
  1812.      *
  1813.      * @param string $realm The URL (or URL pattern) that identifies
  1814.      *  your web site to the user when she is authorizing it.
  1815.      *
  1816.      * @param string $return_to The URL that the OpenID provider will
  1817.      *  send the user back to after attempting to verify her identity.
  1818.      *
  1819.      *  Not specifying a return_to URL means that the user will not be
  1820.      *  returned to the site issuing the request upon its completion.
  1821.      *
  1822.      * @param bool $immediate If true, the OpenID provider is to send
  1823.      *  back a response immediately, useful for behind-the-scenes
  1824.      *  authentication attempts.  Otherwise the OpenID provider may
  1825.      *  engage the user before providing a response.  This is the
  1826.      *  default case, as the user may need to provide credentials or
  1827.      *  approve the request before a positive response can be sent.
  1828.      */
  1829.     function getMessage($realm$return_to=null$immediate=false)
  1830.     {
  1831.         if ($return_to{
  1832.             $return_to Auth_OpenID::appendArgs($return_to,
  1833.                                                  $this->return_to_args);
  1834.         else if ($immediate{
  1835.             // raise ValueError(
  1836.             //     '"return_to" is mandatory when
  1837.             //using "checkid_immediate"')
  1838.             return new Auth_OpenID_FailureResponse(null,
  1839.               "'return_to' is mandatory when using checkid_immediate");
  1840.         else if ($this->message->isOpenID1()) {
  1841.             // raise ValueError('"return_to" is
  1842.             // mandatory for OpenID 1 requests')
  1843.             return new Auth_OpenID_FailureResponse(null,
  1844.               "'return_to' is mandatory for OpenID 1 requests");
  1845.         else if ($this->return_to_args{
  1846.             // raise ValueError('extra "return_to" arguments
  1847.             // were specified, but no return_to was specified')
  1848.             return new Auth_OpenID_FailureResponse(null,
  1849.               "extra 'return_to' arguments where specified, " .
  1850.               "but no return_to was specified");
  1851.         }
  1852.  
  1853.         if ($immediate{
  1854.             $mode 'checkid_immediate';
  1855.         else {
  1856.             $mode 'checkid_setup';
  1857.         }
  1858.  
  1859.         $message $this->message->copy();
  1860.         if ($message->isOpenID1()) {
  1861.             $realm_key 'trust_root';
  1862.         else {
  1863.             $realm_key 'realm';
  1864.         }
  1865.  
  1866.         $message->updateArgs(Auth_OpenID_OPENID_NS,
  1867.                              array(
  1868.                                    $realm_key => $realm,
  1869.                                    'mode' => $mode,
  1870.                                    'return_to' => $return_to));
  1871.  
  1872.         if (!$this->_anonymous{
  1873.             if ($this->endpoint->isOPIdentifier()) {
  1874.                 // This will never happen when we're in compatibility
  1875.                 // mode, as long as isOPIdentifier() returns False
  1876.                 // whenever preferredNamespace() returns OPENID1_NS.
  1877.                 $claimed_id $request_identity =
  1878.                     Auth_OpenID_IDENTIFIER_SELECT;
  1879.             else {
  1880.                 $request_identity $this->endpoint->getLocalID();
  1881.                 $claimed_id $this->endpoint->claimed_id;
  1882.             }
  1883.  
  1884.             // This is true for both OpenID 1 and 2
  1885.             $message->setArg(Auth_OpenID_OPENID_NS'identity',
  1886.                              $request_identity);
  1887.  
  1888.             if ($message->isOpenID2()) {
  1889.                 $message->setArg(Auth_OpenID_OPENID2_NS'claimed_id',
  1890.                                  $claimed_id);
  1891.             }
  1892.         }
  1893.  
  1894.         if ($this->assoc{
  1895.             $message->setArg(Auth_OpenID_OPENID_NS'assoc_handle',
  1896.                              $this->assoc->handle);
  1897.         }
  1898.  
  1899.         return $message;
  1900.     }
  1901.  
  1902.     function redirectURL($realm$return_to null,
  1903.                          $immediate false)
  1904.     {
  1905.         $message $this->getMessage($realm$return_to$immediate);
  1906.  
  1907.         if (Auth_OpenID::isFailure($message)) {
  1908.             return $message;
  1909.         }
  1910.  
  1911.         return $message->toURL($this->endpoint->server_url);
  1912.     }
  1913.  
  1914.     /**
  1915.      * Get html for a form to submit this request to the IDP.
  1916.      *
  1917.      * form_tag_attrs: An array of attributes to be added to the form
  1918.      * tag. 'accept-charset' and 'enctype' have defaults that can be
  1919.      * overridden. If a value is supplied for 'action' or 'method', it
  1920.      * will be replaced.
  1921.      */
  1922.     function formMarkup($realm$return_to=null$immediate=false,
  1923.                         $form_tag_attrs=null)
  1924.     {
  1925.         $message $this->getMessage($realm$return_to$immediate);
  1926.  
  1927.         if (Auth_OpenID::isFailure($message)) {
  1928.             return $message;
  1929.         }
  1930.  
  1931.         return $message->toFormMarkup($this->endpoint->server_url,
  1932.                                       $form_tag_attrs);
  1933.     }
  1934.  
  1935.     /**
  1936.      * Get a complete html document that will autosubmit the request
  1937.      * to the IDP.
  1938.      *
  1939.      * Wraps formMarkup.  See the documentation for that function.
  1940.      */
  1941.     function htmlMarkup($realm$return_to=null$immediate=false,
  1942.                         $form_tag_attrs=null)
  1943.     {
  1944.         $form $this->formMarkup($realm$return_to$immediate
  1945.                                   $form_tag_attrs);
  1946.  
  1947.         if (Auth_OpenID::isFailure($form)) {
  1948.             return $form;
  1949.         }
  1950.         return Auth_OpenID::autoSubmitHTML($form);
  1951.     }
  1952.  
  1953.     function shouldSendRedirect()
  1954.     {
  1955.         return $this->endpoint->compatibilityMode();
  1956.     }
  1957. }
  1958.  
  1959. /**
  1960.  * The base class for responses from the Auth_OpenID_Consumer.
  1961.  *
  1962.  * @package OpenID
  1963.  */
  1964.     var $status = null;
  1965.  
  1966.     function setEndpoint($endpoint)
  1967.     {
  1968.         $this->endpoint $endpoint;
  1969.         if ($endpoint === null{
  1970.             $this->identity_url null;
  1971.         else {
  1972.             $this->identity_url $endpoint->claimed_id;
  1973.         }
  1974.     }
  1975.  
  1976.     /**
  1977.      * Return the display identifier for this response.
  1978.      *
  1979.      * The display identifier is related to the Claimed Identifier, but the
  1980.      * two are not always identical.  The display identifier is something the
  1981.      * user should recognize as what they entered, whereas the response's
  1982.      * claimed identifier (in the identity_url attribute) may have extra
  1983.      * information for better persistence.
  1984.      *
  1985.      * URLs will be stripped of their fragments for display.  XRIs will
  1986.      * display the human-readable identifier (i-name) instead of the
  1987.      * persistent identifier (i-number).
  1988.      *
  1989.      * Use the display identifier in your user interface.  Use
  1990.      * identity_url for querying your database or authorization server.
  1991.      *
  1992.      */
  1993.     function getDisplayIdentifier()
  1994.     {
  1995.         if ($this->endpoint !== null{
  1996.             return $this->endpoint->getDisplayIdentifier();
  1997.         }
  1998.         return null;
  1999.     }
  2000. }
  2001.  
  2002. /**
  2003.  * A response with a status of Auth_OpenID_SUCCESS. Indicates that
  2004.  * this request is a successful acknowledgement from the OpenID server
  2005.  * that the supplied URL is, indeed controlled by the requesting
  2006.  * agent.  This has three relevant attributes:
  2007.  *
  2008.  * claimed_id - The identity URL that has been authenticated
  2009.  *
  2010.  * signed_args - The arguments in the server's response that were
  2011.  * signed and verified.
  2012.  *
  2013.  * status - Auth_OpenID_SUCCESS.
  2014.  *
  2015.  * @package OpenID
  2016.  */
  2017.     var $status = Auth_OpenID_SUCCESS;
  2018.  
  2019.     /**
  2020.      * @access private
  2021.      */
  2022.     function Auth_OpenID_SuccessResponse($endpoint$message$signed_args=null)
  2023.     {
  2024.         $this->endpoint $endpoint;
  2025.         $this->identity_url $endpoint->claimed_id;
  2026.         $this->signed_args $signed_args;
  2027.         $this->message $message;
  2028.  
  2029.         if ($this->signed_args === null{
  2030.             $this->signed_args array();
  2031.         }
  2032.     }
  2033.  
  2034.     /**
  2035.      * Extract signed extension data from the server's response.
  2036.      *
  2037.      * @param string $prefix The extension namespace from which to
  2038.      *  extract the extension data.
  2039.      */
  2040.     function extensionResponse($namespace_uri$require_signed)
  2041.     {
  2042.         if ($require_signed{
  2043.             return $this->getSignedNS($namespace_uri);
  2044.         else {
  2045.             return $this->message->getArgs($namespace_uri);
  2046.         }
  2047.     }
  2048.  
  2049.     function isOpenID1()
  2050.     {
  2051.         return $this->message->isOpenID1();
  2052.     }
  2053.  
  2054.     function isSigned($ns_uri$ns_key)
  2055.     {
  2056.         // Return whether a particular key is signed, regardless of
  2057.         // its namespace alias
  2058.         return in_array($this->message->getKey($ns_uri$ns_key),
  2059.                         $this->signed_args);
  2060.     }
  2061.  
  2062.     function getSigned($ns_uri$ns_key$default null)
  2063.     {
  2064.         // Return the specified signed field if available, otherwise
  2065.         // return default
  2066.         if ($this->isSigned($ns_uri$ns_key)) {
  2067.             return $this->message->getArg($ns_uri$ns_key$default);
  2068.         else {
  2069.             return $default;
  2070.         }
  2071.     }
  2072.  
  2073.     function getSignedNS($ns_uri)
  2074.     {
  2075.         $args array();
  2076.  
  2077.         $msg_args $this->message->getArgs($ns_uri);
  2078.         if (Auth_OpenID::isFailure($msg_args)) {
  2079.             return null;
  2080.         }
  2081.  
  2082.         foreach ($msg_args as $key => $value{
  2083.             if (!$this->isSigned($ns_uri$key)) {
  2084.                 return null;
  2085.             }
  2086.         }
  2087.  
  2088.         return $msg_args;
  2089.     }
  2090.  
  2091.     /**
  2092.      * Get the openid.return_to argument from this response.
  2093.      *
  2094.      * This is useful for verifying that this request was initiated by
  2095.      * this consumer.
  2096.      *
  2097.      * @return string $return_to The return_to URL supplied to the
  2098.      *  server on the initial request, or null if the response did not
  2099.      *  contain an 'openid.return_to' argument.
  2100.     */
  2101.     function getReturnTo()
  2102.     {
  2103.         return $this->getSigned(Auth_OpenID_OPENID_NS'return_to');
  2104.     }
  2105. }
  2106.  
  2107. /**
  2108.  * A response with a status of Auth_OpenID_FAILURE. Indicates that the
  2109.  * OpenID protocol has failed. This could be locally or remotely
  2110.  * triggered.  This has three relevant attributes:
  2111.  *
  2112.  * claimed_id - The identity URL for which authentication was
  2113.  * attempted, if it can be determined.  Otherwise, null.
  2114.  *
  2115.  * message - A message indicating why the request failed, if one is
  2116.  * supplied.  Otherwise, null.
  2117.  *
  2118.  * status - Auth_OpenID_FAILURE.
  2119.  *
  2120.  * @package OpenID
  2121.  */
  2122.     var $status = Auth_OpenID_FAILURE;
  2123.  
  2124.     function Auth_OpenID_FailureResponse($endpoint$message null,