Support Joomla!

Joomla! 1.5 Documentation

Packages

Package: OpenID

Developer Network License

The Joomla! Developer Network content is © copyright 2006 by the individual contributors and can be used in accordance with the Creative Commons License, Attribution- NonCommercial- ShareAlike 2.5
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.  In addition to saying that, the identity server
  101.  * provides a URL to which the user can be sent to provide the needed
  102.  * information and let the server finish handling the original
  103.  * request.
  104.  *
  105.  * USING THIS LIBRARY
  106.  *
  107.  * Integrating this library into an application is usually a
  108.  * relatively straightforward process.  The process should basically
  109.  * follow this plan:
  110.  *
  111.  * Add an OpenID login field somewhere on your site.  When an OpenID
  112.  * is entered in that field and the form is submitted, it should make
  113.  * a request to the your site which includes that OpenID URL.
  114.  *
  115.  * First, the application should instantiate the Auth_OpenID_Consumer
  116.  * class using the store of choice (Auth_OpenID_FileStore or one of
  117.  * the SQL-based stores).  If the application has any sort of session
  118.  * framework that provides per-client state management, a dict-like
  119.  * object to access the session should be passed as the optional
  120.  * second parameter.  (The default behavior is to use PHP's standard
  121.  * session machinery.)
  122.  *
  123.  * Next, the application should call the Auth_OpenID_Consumer object's
  124.  * 'begin' method.  This method takes the OpenID URL.  The 'begin'
  125.  * method returns an Auth_OpenID_AuthRequest object.
  126.  *
  127.  * Next, the application should call the 'redirectURL' method of the
  128.  * Auth_OpenID_AuthRequest object.  The 'return_to' URL parameter is
  129.  * the URL that the OpenID server will send the user back to after
  130.  * attempting to verify his or her identity.  The 'trust_root' is the
  131.  * URL (or URL pattern) that identifies your web site to the user when
  132.  * he or she is authorizing it.  Send a redirect to the resulting URL
  133.  * to the user's browser.
  134.  *
  135.  * That's the first half of the authentication process.  The second
  136.  * half of the process is done after the user's ID server sends the
  137.  * user's browser a redirect back to your site to complete their
  138.  * login.
  139.  *
  140.  * When that happens, the user will contact your site at the URL given
  141.  * as the 'return_to' URL to the Auth_OpenID_AuthRequest::redirectURL
  142.  * call made above.  The request will have several query parameters
  143.  * added to the URL by the identity server as the information
  144.  * necessary to finish the request.
  145.  *
  146.  * Lastly, instantiate an Auth_OpenID_Consumer instance as above and
  147.  * call its 'complete' method, passing in all the received query
  148.  * arguments.
  149.  *
  150.  * There are multiple possible return types possible from that
  151.  * method. These indicate the whether or not the login was successful,
  152.  * and include any additional information appropriate for their type.
  153.  *
  154.  * PHP versions 4 and 5
  155.  *
  156.  * LICENSE: See the COPYING file included in this distribution.
  157.  *
  158.  * @package OpenID
  159.  * @author JanRain, Inc. <[email protected]>
  160.  * @copyright 2005 Janrain, Inc.
  161.  * @license http://www.gnu.org/copyleft/lesser.html LGPL
  162.  */
  163.  
  164. /**
  165.  * Require utility classes and functions for the consumer.
  166.  */
  167. require_once "Auth/OpenID.php";
  168. require_once "Auth/OpenID/HMACSHA1.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/Discover.php";
  174. require_once "Services/Yadis/Manager.php";
  175. require_once "Services/Yadis/XRI.php";
  176.  
  177. /**
  178.  * This is the status code returned when the complete method returns
  179.  * successfully.
  180.  */
  181. define('Auth_OpenID_SUCCESS''success');
  182.  
  183. /**
  184.  * Status to indicate cancellation of OpenID authentication.
  185.  */
  186. define('Auth_OpenID_CANCEL''cancel');
  187.  
  188. /**
  189.  * This is the status code completeAuth returns when the value it
  190.  * received indicated an invalid login.
  191.  */
  192. define('Auth_OpenID_FAILURE''failure');
  193.  
  194. /**
  195.  * This is the status code completeAuth returns when the
  196.  * {@link Auth_OpenID_Consumer} instance is in immediate mode, and the
  197.  * identity server sends back a URL to send the user to to complete his
  198.  * or her login.
  199.  */
  200. define('Auth_OpenID_SETUP_NEEDED''setup needed');
  201.  
  202. /**
  203.  * This is the status code beginAuth returns when the page fetched
  204.  * from the entered OpenID URL doesn't contain the necessary link tags
  205.  * to function as an identity page.
  206.  */
  207. define('Auth_OpenID_PARSE_ERROR''parse error');
  208.  
  209. /**
  210.  * This is the characters that the nonces are made from.
  211.  */
  212. define('Auth_OpenID_DEFAULT_NONCE_CHRS',"abcdefghijklmnopqrstuvwxyz" .
  213.        "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
  214.  
  215. /**
  216.  * An OpenID consumer implementation that performs discovery and does
  217.  * session management.  See the Consumer.php file documentation for
  218.  * more information.
  219.  *
  220.  * @package OpenID
  221.  */
  222.  
  223.     /**
  224.      * @access private
  225.      */
  226.     var $session_key_prefix "_openid_consumer_";
  227.  
  228.     /**
  229.      * @access private
  230.      */
  231.     var $_token_suffix "last_token";
  232.  
  233.     /**
  234.      * Initialize a Consumer instance.
  235.      *
  236.      * You should create a new instance of the Consumer object with
  237.      * every HTTP request that handles OpenID transactions.
  238.      *
  239.      * @param Auth_OpenID_OpenIDStore $store This must be an object
  240.      *  that implements the interface in {@link }
  241.      *  Auth_OpenID_OpenIDStore}.  Several concrete implementations are
  242.      *  provided, to cover most common use cases.  For stores backed by
  243.      *  MySQL, PostgreSQL, or SQLite, see the {@link }
  244.      *  Auth_OpenID_SQLStore} class and its sublcasses.  For a
  245.      *  filesystem-backed store, see the {@link Auth_OpenID_FileStore}
  246.      *  module.  As a last resort, if it isn't possible for the server
  247.      *  to store state at all, an instance of {@link }
  248.      *  Auth_OpenID_DumbStore} can be used.
  249.      *
  250.      * @param mixed session An object which implements the interface
  251.      *  of the Services_Yadis_Session class.  Particularly, this object
  252.      *  is expected to have these methods: get($key), set($key,
  253.      *  $value), and del($key).  This defaults to a session object
  254.      *  which wraps PHP's native session machinery.  You should only
  255.      *  need to pass something here if you have your own sessioning
  256.      *  implementation.
  257.      */
  258.     function Auth_OpenID_Consumer(&$store$session null)
  259.     {
  260.         if ($session === null{
  261.             $session new Services_Yadis_PHPSession();
  262.         }
  263.  
  264.         $this->session =$session;
  265.         $this->consumer =new Auth_OpenID_GenericConsumer($store);
  266.         $this->_token_key $this->session_key_prefix $this->_token_suffix;
  267.     }
  268.  
  269.     /**
  270.      * Start the OpenID authentication process. See steps 1-2 in the
  271.      * overview at the top of this file.
  272.      *
  273.      * @param User_url: Identity URL given by the user. This method
  274.      *  performs a textual transformation of the URL to try and make
  275.      *  sure it is normalized. For example, a user_url of example.com
  276.      *  will be normalized to http://example.com/ normalizing and
  277.      *  resolving any redirects the server might issue.
  278.      *
  279.      * @return Auth_OpenID_AuthRequest $auth_request An object
  280.      *  containing the discovered information will be returned, with a
  281.      *  method for building a redirect URL to the server, as described
  282.      *  in step 3 of the overview. This object may also be used to add
  283.      *  extension arguments to the request, using its 'addExtensionArg'
  284.      *  method.
  285.      */
  286.     function begin($user_url)
  287.     {
  288.         $discoverMethod '_Auth_OpenID_discoverServiceList';
  289.         $openid_url $user_url;
  290.  
  291.         if (Services_Yadis_identifierScheme($user_url== 'XRI'{
  292.             $discoverMethod '_Auth_OpenID_discoverXRIServiceList';
  293.         else {
  294.             $openid_url Auth_OpenID::normalizeUrl($user_url);
  295.         }
  296.  
  297.         $disco =new Services_Yadis_Discovery($this->session,
  298.                                                $openid_url,
  299.                                                $this->session_key_prefix);
  300.  
  301.         // Set the 'stale' attribute of the manager.  If discovery
  302.         // fails in a fatal way, the stale flag will cause the manager
  303.         // to be cleaned up next time discovery is attempted.
  304.  
  305.         $m $disco->getManager();
  306.         $loader new Services_Yadis_ManagerLoader();
  307.  
  308.         if ($m{
  309.             if ($m->stale{
  310.                 $disco->destroyManager();
  311.             else {
  312.                 $m->stale true;
  313.                 $disco->session->set($disco->session_key,
  314.                                      serialize($loader->toSession($m)));
  315.             }
  316.         }
  317.  
  318.         $endpoint $disco->getNextService($discoverMethod,
  319.                                            $this->consumer->fetcher);
  320.  
  321.         // Reset the 'stale' attribute of the manager.
  322.         $m =$disco->getManager();
  323.         if ($m{
  324.             $m->stale false;
  325.             $disco->session->set($disco->session_key,
  326.                                  serialize($loader->toSession($m)));
  327.         }
  328.  
  329.         if ($endpoint === null{
  330.             return null;
  331.         else {
  332.             return $this->beginWithoutDiscovery($endpoint);
  333.         }
  334.     }
  335.  
  336.     /**
  337.      * Start OpenID verification without doing OpenID server
  338.      * discovery. This method is used internally by Consumer.begin
  339.      * after discovery is performed, and exists to provide an
  340.      * interface for library users needing to perform their own
  341.      * discovery.
  342.      *
  343.      * @param Auth_OpenID_ServiceEndpoint $endpoint an OpenID service
  344.      *  endpoint descriptor.
  345.      *
  346.      * @return Auth_OpenID_AuthRequest $auth_request An OpenID
  347.      *  authentication request object.
  348.      */
  349.     function &beginWithoutDiscovery($endpoint)
  350.     {
  351.         $loader new Auth_OpenID_ServiceEndpointLoader();
  352.         $auth_req $this->consumer->begin($endpoint);
  353.         $this->session->set($this->_token_key,
  354.               $loader->toSession($auth_req->endpoint));
  355.         return $auth_req;
  356.     }
  357.  
  358.     /**
  359.      * Called to interpret the server's response to an OpenID
  360.      * request. It is called in step 4 of the flow described in the
  361.      * consumer overview.
  362.      *
  363.      * @param array $query An array of the query parameters (key =>
  364.      *  value pairs) for this HTTP request.
  365.      *
  366.      * @return Auth_OpenID_ConsumerResponse $response A instance of an
  367.      *  Auth_OpenID_ConsumerResponse subclass. The type of response is
  368.      *  indicated by the status attribute, which will be one of
  369.      *  SUCCESS, CANCEL, FAILURE, or SETUP_NEEDED.
  370.      */
  371.     function complete($query)
  372.     {
  373.         $query Auth_OpenID::fixArgs($query);
  374.  
  375.         $loader new Auth_OpenID_ServiceEndpointLoader();
  376.         $endpoint_data $this->session->get($this->_token_key);
  377.         $endpoint =
  378.             $loader->fromSession($endpoint_data);
  379.  
  380.         if ($endpoint === null{
  381.             $response new Auth_OpenID_FailureResponse(null,
  382.                                                    'No session state found');
  383.         else {
  384.             $response $this->consumer->complete($query$endpoint);
  385.             $this->session->del($this->_token_key);
  386.         }
  387.  
  388.         if (in_array($response->statusarray(Auth_OpenID_SUCCESS,
  389.                                               Auth_OpenID_CANCEL))) {
  390.             if ($response->identity_url !== null{
  391.                 $disco new Services_Yadis_Discovery($this->session,
  392.                                                   $response->identity_url,
  393.                                                   $this->session_key_prefix);
  394.                 $disco->cleanup();
  395.             }
  396.         }
  397.  
  398.         return $response;
  399.     }
  400. }
  401.  
  402.     var $session_type = 'DH-SHA1';
  403.  
  404.     function Auth_OpenID_DiffieHellmanConsumerSession($dh null)
  405.     {
  406.         if ($dh === null{
  407.             $dh new Auth_OpenID_DiffieHellman();
  408.         }
  409.  
  410.         $this->dh $dh;
  411.     }
  412.  
  413.     function getRequest()
  414.     {
  415.         $math =Auth_OpenID_getMathLib();
  416.  
  417.         $cpub $math->longToBase64($this->dh->public);
  418.  
  419.         $args array('openid.dh_consumer_public' => $cpub);
  420.  
  421.         if (!$this->dh->usingDefaultValues()) {
  422.             $args array_merge($argsarray(
  423.                 'openid.dh_modulus' =>
  424.                      $math->longToBase64($this->dh->mod),
  425.                 'openid.dh_gen' =>
  426.                 $math->longToBase64($this->dh->gen)));
  427.         }
  428.  
  429.         return $args;
  430.     }
  431.  
  432.     function extractSecret($response)
  433.     {
  434.         if (!array_key_exists('dh_server_public'$response)) {
  435.             return null;
  436.         }
  437.  
  438.         if (!array_key_exists('enc_mac_key'$response)) {
  439.             return null;
  440.         }
  441.  
  442.         $math =Auth_OpenID_getMathLib();
  443.         $spub $math->base64ToLong($response['dh_server_public']);
  444.         $enc_mac_key base64_decode($response['enc_mac_key']);
  445.  
  446.         return $this->dh->xorSecret($spub$enc_mac_key);
  447.     }
  448. }
  449.  
  450.     var $session_type = null;
  451.  
  452.     function getRequest()
  453.     {
  454.         return array();
  455.     }
  456.  
  457.     function extractSecret($response)
  458.     {
  459.         if (!array_key_exists('mac_key'$response)) {
  460.             return null;
  461.         }
  462.  
  463.         return base64_decode($response['mac_key']);
  464.     }
  465. }
  466.  
  467. /**
  468.  * This class is the interface to the OpenID consumer logic.
  469.  * Instances of it maintain no per-request state, so they can be
  470.  * reused (or even used by multiple threads concurrently) as needed.
  471.  *
  472.  * @package OpenID
  473.  * @access private
  474.  */
  475. class Auth_OpenID_GenericConsumer {
  476.     /**
  477.      * This consumer's store object.
  478.      */
  479.     var $store;
  480.  
  481.     /**
  482.      * @access private
  483.      */
  484.     var $_use_assocs;
  485.  
  486.     /**
  487.      * This is the number of characters in the generated nonce for
  488.      * each transaction.
  489.      */
  490.     var $nonce_len 8;
  491.  
  492.     /**
  493.      * What characters are allowed in nonces
  494.      */
  495.     var $nonce_chrs Auth_OpenID_DEFAULT_NONCE_CHRS;
  496.  
  497.     /**
  498.      * This method initializes a new {@link Auth_OpenID_Consumer}
  499.      * instance to access the library.
  500.      *
  501.      * @param Auth_OpenID_OpenIDStore $store This must be an object
  502.      *  that implements the interface in {@link Auth_OpenID_OpenIDStore}.
  503.      *  Several concrete implementations are provided, to cover most common use
  504.      *  cases.  For stores backed by MySQL, PostgreSQL, or SQLite, see
  505.      *  the {@link Auth_OpenID_SQLStore} class and its sublcasses.  For a
  506.      *  filesystem-backed store, see the {@link Auth_OpenID_FileStore} module.
  507.      *  As a last resort, if it isn't possible for the server to store
  508.      *  state at all, an instance of {@link Auth_OpenID_DumbStore} can be used.
  509.      *
  510.      * @param bool $immediate This is an optional boolean value.  It
  511.      *  controls whether the library uses immediate mode, as explained
  512.      *  in the module description.  The default value is False, which
  513.      *  disables immediate mode.
  514.      */
  515.     function Auth_OpenID_GenericConsumer(&$store)
  516.     {
  517.         $this->store =$store;
  518.         $this->_use_assocs =
  519.             !(defined('Auth_OpenID_NO_MATH_SUPPORT'||
  520.               ($this->store && $this->store->isDumb()));
  521.  
  522.         $this->fetcher Services_Yadis_Yadis::getHTTPFetcher();
  523.     }
  524.  
  525.     function begin($service_endpoint)
  526.     {
  527.         $nonce $this->_createNonce();
  528.         $assoc $this->_getAssociation($service_endpoint->server_url);
  529.         $r new Auth_OpenID_AuthRequest($assoc$service_endpoint);
  530.         $r->return_to_args['nonce'$nonce;
  531.         return $r;
  532.     }
  533.  
  534.     function complete($query$endpoint)
  535.     {
  536.         $mode Auth_OpenID::arrayGet($query'openid.mode',
  537.                                       '<no mode specified>');
  538.  
  539.         if ($mode == Auth_OpenID_CANCEL{
  540.             return new Auth_OpenID_CancelResponse($endpoint);
  541.         else if ($mode == 'error'{
  542.             $error Auth_OpenID::arrayGet($query'openid.error');
  543.             return new Auth_OpenID_FailureResponse($endpoint$error);
  544.         else if ($mode == 'id_res'{
  545.             if ($endpoint->identity_url === null{
  546.                 return new Auth_OpenID_FailureResponse($identity_url,
  547.                                                "No session state found");
  548.             }
  549.  
  550.             $response $this->_doIdRes($query$endpoint);
  551.  
  552.             if ($response === null{
  553.                 return new Auth_OpenID_FailureResponse($endpoint,
  554.                                                        "HTTP request failed");
  555.             }
  556.             if ($response->status == Auth_OpenID_SUCCESS{
  557.                 return $this->_checkNonce($response,
  558.                                           Auth_OpenID::arrayGet($query,
  559.                                                                 'nonce'));
  560.             else {
  561.                 return $response;
  562.             }
  563.         else {
  564.             return new Auth_OpenID_FailureResponse($endpoint,
  565.                                            sprintf("Invalid openid.mode '%s'",
  566.                                                    $mode));
  567.         }
  568.     }
  569.  
  570.     /**
  571.      * @access private
  572.      */
  573.     function _doIdRes($query$endpoint)
  574.     {
  575.         $user_setup_url Auth_OpenID::arrayGet($query,
  576.                                                 'openid.user_setup_url');
  577.  
  578.         if ($user_setup_url !== null{
  579.             return new Auth_OpenID_SetupNeededResponse($endpoint,
  580.                                                        $user_setup_url);
  581.         }
  582.  
  583.         $return_to Auth_OpenID::arrayGet($query'openid.return_to'null);
  584.         $server_id2 Auth_OpenID::arrayGet($query'openid.identity'null);
  585.         $assoc_handle Auth_OpenID::arrayGet($query,
  586.                                              'openid.assoc_handle'null);
  587.  
  588.         if (($return_to === null||
  589.             ($server_id2 === null||
  590.             ($assoc_handle === null)) {
  591.             return new Auth_OpenID_FailureResponse($endpoint,
  592.                                                    "Missing required field");
  593.         }
  594.  
  595.         if ($endpoint->getServerID(!= $server_id2{
  596.             return new Auth_OpenID_FailureResponse($endpoint,
  597.                                              "Server ID (delegate) mismatch");
  598.         }
  599.  
  600.         $signed Auth_OpenID::arrayGet($query'openid.signed');
  601.  
  602.         $assoc $this->store->getAssociation($endpoint->server_url,
  603.                                               $assoc_handle);
  604.  
  605.         if ($assoc === null{
  606.             // It's not an association we know about.  Dumb mode is
  607.             // our only possible path for recovery.
  608.             if ($this->_checkAuth($query$endpoint->server_url)) {
  609.                 return new Auth_OpenID_SuccessResponse($endpoint$query,
  610.                                                        $signed);
  611.             else {
  612.                 return new Auth_OpenID_FailureResponse($endpoint,
  613.                                        "Server denied check_authentication");
  614.             }
  615.         }
  616.  
  617.         if ($assoc->getExpiresIn(<= 0{
  618.             $msg sprintf("Association with %s expired",
  619.                            $endpoint->server_url);
  620.             return new Auth_OpenID_FailureResponse($endpoint$msg);
  621.         }
  622.  
  623.         // Check the signature
  624.         $sig Auth_OpenID::arrayGet($query'openid.sig'null);
  625.         if (($sig === null||
  626.             ($signed === null)) {
  627.             return new Auth_OpenID_FailureResponse($endpoint,
  628.                                                "Missing argument signature");
  629.         }
  630.  
  631.         $signed_list explode(","$signed);
  632.  
  633.         //Fail if the identity field is present but not signed
  634.         if (($endpoint->identity_url !== null&&
  635.             (!in_array('identity'$signed_list))) {
  636.             $msg '"openid.identity" not signed';
  637.             return new Auth_OpenID_FailureResponse($endpoint$msg);
  638.         }
  639.  
  640.         $v_sig $assoc->signDict($signed_list$query);
  641.  
  642.         if ($v_sig != $sig{
  643.             return new Auth_OpenID_FailureResponse($endpoint,
  644.                                                    "Bad signature");
  645.         }
  646.  
  647.         return Auth_OpenID_SuccessResponse::fromQuery($endpoint,
  648.                                                       $query$signed);
  649.     }
  650.  
  651.     /**
  652.      * @access private
  653.      */
  654.     function _checkAuth($query$server_url)
  655.     {
  656.         $request $this->_createCheckAuthRequest($query);
  657.         if ($request === null{
  658.             return false;
  659.         }
  660.  
  661.         $response $this->_makeKVPost($request$server_url);
  662.         if ($response == null{
  663.             return false;
  664.         }
  665.  
  666.         return $this->_processCheckAuthResponse($response$server_url);
  667.     }
  668.  
  669.     /**
  670.      * @access private
  671.      */
  672.     function _createCheckAuthRequest($query)
  673.     {
  674.         $signed Auth_OpenID::arrayGet($query'openid.signed'null);
  675.         if ($signed === null{
  676.             return null;
  677.         }
  678.  
  679.         $whitelist array('assoc_handle''sig',
  680.                            'signed''invalidate_handle');
  681.  
  682.         $signed array_merge(explode(","$signed)$whitelist);
  683.  
  684.         $check_args array();
  685.  
  686.         foreach ($query as $key => $value{
  687.             if (in_array(substr($key7)$signed)) {
  688.                 $check_args[$key$value;
  689.             }
  690.         }
  691.  
  692.         $check_args['openid.mode''check_authentication';
  693.         return $check_args;
  694.     }
  695.  
  696.     /**
  697.      * @access private
  698.      */
  699.     function _processCheckAuthResponse($response$server_url)
  700.     {
  701.         $is_valid Auth_OpenID::arrayGet($response'is_valid''false');
  702.  
  703.         $invalidate_handle Auth_OpenID::arrayGet($response,
  704.                                                    'invalidate_handle');
  705.  
  706.         if ($invalidate_handle !== null{
  707.             $this->store->removeAssociation($server_url,
  708.                                             $invalidate_handle);
  709.         }
  710.  
  711.         if ($is_valid == 'true'{
  712.             return true;
  713.         }
  714.  
  715.         return false;
  716.     }
  717.  
  718.     /**
  719.      * @access private
  720.      */
  721.     function _makeKVPost($args$server_url)
  722.     {
  723.         $mode $args['openid.mode'];
  724.  
  725.         $pairs array();
  726.         foreach ($args as $k => $v{
  727.             $v urlencode($v);
  728.             $pairs["$k=$v";
  729.         }
  730.  
  731.         $body implode("&"$pairs);
  732.  
  733.         $resp $this->fetcher->post($server_url$body);
  734.  
  735.         if ($resp === null{
  736.             return null;
  737.         }
  738.  
  739.         $response Auth_OpenID_KVForm::toArray($resp->body);
  740.  
  741.         if ($resp->status == 400{
  742.             return null;
  743.         else if ($resp->status != 200{
  744.             return null;
  745.         }
  746.  
  747.         return $response;
  748.     }
  749.  
  750.     /**
  751.      * @access private
  752.      */
  753.     function _checkNonce($response$nonce)
  754.     {
  755.         $parsed_url parse_url($response->getReturnTo());
  756.         $query_str @$parsed_url['query'];
  757.         $query array();
  758.         parse_str($query_str$query);
  759.  
  760.         $found false;
  761.  
  762.         foreach ($query as $k => $v{
  763.             if ($k == 'nonce'{
  764.                 if ($v != $nonce{
  765.                     return new Auth_OpenID_FailureResponse($response,
  766.                                                            "Nonce mismatch");
  767.                 else {
  768.                     $found true;
  769.                     break;
  770.                 }
  771.             }
  772.         }
  773.  
  774.         if (!$found{
  775.             return new Auth_OpenID_FailureResponse($response,
  776.                                  sprintf("Nonce missing from return_to: %s",
  777.                                          $response->getReturnTo()));
  778.         }
  779.  
  780.         if (!$this->store->useNonce($nonce)) {
  781.             return new Auth_OpenID_FailureResponse($response,
  782.                                                    "Nonce missing from store");
  783.         }
  784.  
  785.         return $response;
  786.     }
  787.  
  788.     /**
  789.      * @access private
  790.      */
  791.     function _createNonce()
  792.     {
  793.         $nonce Auth_OpenID_CryptUtil::randomString($this->nonce_len,
  794.                                                      $this->nonce_chrs);
  795.         $this->store->storeNonce($nonce);
  796.         return $nonce;
  797.     }
  798.  
  799.     /**
  800.      * @access protected
  801.      */
  802.     function _createDiffieHellman()
  803.     {
  804.         return new Auth_OpenID_DiffieHellman();
  805.     }
  806.  
  807.     /**
  808.      * @access private
  809.      */
  810.     function _getAssociation($server_url)
  811.     {
  812.         if (!$this->_use_assocs{
  813.             return null;
  814.         }
  815.  
  816.         $assoc $this->store->getAssociation($server_url);
  817.  
  818.         if (($assoc === null||
  819.             ($assoc->getExpiresIn(<= 0)) {
  820.  
  821.             $parts $this->_createAssociateRequest($server_url);
  822.  
  823.             if ($parts === null{
  824.                 return null;
  825.             }
  826.  
  827.             list($assoc_session$args$parts;
  828.  
  829.             $response $this->_makeKVPost($args$server_url);
  830.  
  831.             if ($response === null{
  832.                 $assoc null;
  833.             else {
  834.                 $assoc $this->_parseAssociation($response$assoc_session,
  835.                                                   $server_url);
  836.             }
  837.         }
  838.  
  839.         return $assoc;
  840.     }
  841.  
  842.     function _createAssociateRequest($server_url)
  843.     {
  844.         $parts parse_url($server_url);
  845.  
  846.         if ($parts === false{
  847.             return null;
  848.         }
  849.  
  850.         if (array_key_exists('scheme'$parts)) {
  851.             $proto $parts['scheme'];
  852.         else {
  853.             $proto 'http';
  854.         }
  855.  
  856.         if ($proto == 'https'{
  857.             $assoc_session new Auth_OpenID_PlainTextConsumerSession();
  858.         else {
  859.             $assoc_session new Auth_OpenID_DiffieHellmanConsumerSession();
  860.         }
  861.  
  862.         $args array(
  863.             'openid.mode' => 'associate',
  864.             'openid.assoc_type' => 'HMAC-SHA1');
  865.  
  866.         if ($assoc_session->session_type !== null{
  867.             $args['openid.session_type'$assoc_session->session_type;
  868.         }
  869.  
  870.         $args array_merge($args$assoc_session->getRequest());
  871.         return array($assoc_session$args);
  872.     }
  873.  
  874.     /**
  875.      * @access private
  876.      */
  877.     function _parseAssociation($results$assoc_session$server_url)
  878.     {
  879.         $required_keys array('assoc_type''assoc_handle',
  880.                                'expires_in');
  881.  
  882.         foreach ($required_keys as $key{
  883.             if (!array_key_exists($key$results)) {
  884.                 return null;
  885.             }
  886.         }
  887.  
  888.         $assoc_type $results['assoc_type'];
  889.         $assoc_handle $results['assoc_handle'];
  890.         $expires_in_str $results['expires_in'];
  891.  
  892.         if ($assoc_type != 'HMAC-SHA1'{
  893.             return null;
  894.         }
  895.  
  896.         $expires_in intval($expires_in_str);
  897.  
  898.         if ($expires_in <= 0{
  899.             return null;
  900.         }
  901.  
  902.         $session_type Auth_OpenID::arrayGet($results'session_type');
  903.         if ($session_type != $assoc_session->session_type{
  904.             if ($session_type === null{
  905.                 $assoc_session new Auth_OpenID_PlainTextConsumerSession();
  906.             else {
  907.                 return null;
  908.             }
  909.         }
  910.  
  911.         $secret $assoc_session->extractSecret($results);
  912.  
  913.         if (!$secret{
  914.             return null;
  915.         }
  916.  
  917.         $assoc Auth_OpenID_Association::fromExpiresIn(
  918.                          $expires_in$assoc_handle$secret$assoc_type);
  919.         $this->store->storeAssociation($server_url$assoc);
  920.  
  921.         return $assoc;
  922.     }
  923. }
  924.  
  925. /**
  926.  * This class represents an authentication request from a consumer to
  927.  * an OpenID server.
  928.  *
  929.  * @package OpenID
  930.  */
  931.  
  932.     /**
  933.      * Initialize an authentication request with the specified token,
  934.      * association, and endpoint.
  935.      *
  936.      * Users of this library should not create instances of this
  937.      * class.  Instances of this class are created by the library when
  938.      * needed.
  939.      */
  940.     function Auth_OpenID_AuthRequest($assoc$endpoint)
  941.     {
  942.         $this->assoc $assoc;
  943.         $this->endpoint $endpoint;
  944.         $this->extra_args array();
  945.         $this->return_to_args array();
  946.     }
  947.  
  948.     /**
  949.      * Add an extension argument to this OpenID authentication
  950.      * request.
  951.      *
  952.      * Use caution when adding arguments, because they will be
  953.      * URL-escaped and appended to the redirect URL, which can easily
  954.      * get quite long.
  955.      *
  956.      * @param string $namespace The namespace for the extension. For
  957.      *  example, the simple registration extension uses the namespace
  958.      *  'sreg'.
  959.      *
  960.      * @param string $key The key within the extension namespace. For
  961.      *  example, the nickname field in the simple registration
  962.      *  extension's key is 'nickname'.
  963.      *
  964.      * @param string $value The value to provide to the server for
  965.      *  this argument.
  966.      */
  967.     function addExtensionArg($namespace$key$value)
  968.     {
  969.         $arg_name implode('.'array('openid'$namespace$key));
  970.         $this->extra_args[$arg_name$value;
  971.     }
  972.  
  973.     /**
  974.      * Compute the appropriate redirection URL for this request based
  975.      * on a specified trust root and return-to.
  976.      *
  977.      * @param string $trust_root The trust root URI for your
  978.      *  application.
  979.      *
  980.      * @param string$ $return_to The return-to URL to be used when the
  981.      *  OpenID server redirects the user back to your site.
  982.      *
  983.      * @return string $redirect_url The resulting redirect URL that
  984.      *  you should send to the user agent.
  985.      */
  986.     function redirectURL($trust_root$return_to$immediate=false)
  987.     {
  988.         if ($immediate{
  989.             $mode 'checkid_immediate';
  990.         else {
  991.             $mode 'checkid_setup';
  992.         }
  993.  
  994.         $return_to Auth_OpenID::appendArgs($return_to$this->return_to_args);
  995.  
  996.         $redir_args array(
  997.             'openid.mode' => $mode,
  998.             'openid.identity' => $this->endpoint->getServerID(),
  999.             'openid.return_to' => $return_to,
  1000.             'openid.trust_root' => $trust_root);
  1001.  
  1002.         if ($this->assoc{
  1003.             $redir_args['openid.assoc_handle'$this->assoc->handle;
  1004.         }
  1005.  
  1006.         $redir_args array_merge($redir_args$this->extra_args);
  1007.  
  1008.         return Auth_OpenID::appendArgs($this->endpoint->server_url,
  1009.                                        $redir_args);
  1010.     }
  1011. }
  1012.  
  1013. /**
  1014.  * The base class for responses from the Auth_OpenID_Consumer.
  1015.  *
  1016.  * @package OpenID
  1017.  */
  1018.     var $status = null;
  1019. }
  1020.  
  1021. /**
  1022.  * A response with a status of Auth_OpenID_SUCCESS. Indicates that
  1023.  * this request is a successful acknowledgement from the OpenID server
  1024.  * that the supplied URL is, indeed controlled by the requesting
  1025.  * agent.  This has three relevant attributes:
  1026.  *
  1027.  * identity_url - The identity URL that has been authenticated
  1028.  *
  1029.  * signed_args - The arguments in the server's response that were
  1030.  * signed and verified.
  1031.  *
  1032.  * status - Auth_OpenID_SUCCESS.
  1033.  *
  1034.  * @package OpenID
  1035.  */
  1036.     var $status = Auth_OpenID_SUCCESS;
  1037.  
  1038.     /**
  1039.      * @access private
  1040.      */
  1041.     function Auth_OpenID_SuccessResponse($endpoint$signed_args)
  1042.     {
  1043.         $this->endpoint $endpoint;
  1044.         $this->identity_url $endpoint->identity_url;
  1045.         $this->signed_args $signed_args;
  1046.     }
  1047.  
  1048.     /**
  1049.      * @access private
  1050.      */
  1051.     function fromQuery($endpoint$query$signed)
  1052.     {
  1053.         $signed_args array();
  1054.         foreach (explode(","$signedas $field_name{
  1055.             $field_name 'openid.' $field_name;
  1056.             $signed_args[$field_nameAuth_OpenID::arrayGet($query,
  1057.                                                               $field_name'');
  1058.         }
  1059.         return new Auth_OpenID_SuccessResponse($endpoint$signed_args);
  1060.     }
  1061.  
  1062.     /**
  1063.      * Extract signed extension data from the server's response.
  1064.      *
  1065.      * @param string $prefix The extension namespace from which to
  1066.      *  extract the extension data.
  1067.      */
  1068.     function extensionResponse($prefix)
  1069.     {
  1070.         $response array();
  1071.         $prefix sprintf('openid.%s.'$prefix);
  1072.         $prefix_len strlen($prefix);
  1073.         foreach ($this->signed_args as $k => $v{
  1074.             if (strpos($k$prefix=== 0{
  1075.                 $response_key substr($k$prefix_len);
  1076.                 $response[$response_key$v;
  1077.             }
  1078.         }
  1079.  
  1080.         return $response;
  1081.     }
  1082.  
  1083.     /**
  1084.      * Get the openid.return_to argument from this response.
  1085.      *
  1086.      * This is useful for verifying that this request was initiated by
  1087.      * this consumer.
  1088.      *
  1089.      * @return string $return_to The return_to URL supplied to the
  1090.      *  server on the initial request, or null if the response did not
  1091.      *  contain an 'openid.return_to' argument.
  1092.     */
  1093.     function getReturnTo()
  1094.     {
  1095.         return Auth_OpenID::arrayGet($this->signed_args'openid.return_to');
  1096.     }
  1097. }
  1098.  
  1099. /**
  1100.  * A response with a status of Auth_OpenID_FAILURE. Indicates that the
  1101.  * OpenID protocol has failed. This could be locally or remotely
  1102.  * triggered.  This has three relevant attributes:
  1103.  *
  1104.  * identity_url - The identity URL for which authentication was
  1105.  * attempted, if it can be determined.  Otherwise, null.
  1106.  *
  1107.  * message - A message indicating why the request failed, if one is
  1108.  * supplied.  Otherwise, null.
  1109.  *
  1110.  * status - Auth_OpenID_FAILURE.
  1111.  *
  1112.  * @package OpenID
  1113.  */
  1114.     var $status = Auth_OpenID_FAILURE;
  1115.  
  1116.     function Auth_OpenID_FailureResponse($endpoint$message null)
  1117.     {
  1118.         $this->endpoint $endpoint;
  1119.         if ($endpoint !== null{
  1120.             $this->identity_url $endpoint->identity_url;
  1121.         else {
  1122.             $this->identity_url null;
  1123.         }
  1124.         $this->message $message;
  1125.     }
  1126. }
  1127.  
  1128. /**
  1129.  * A response with a status of Auth_OpenID_CANCEL. Indicates that the
  1130.  * user cancelled the OpenID authentication request.  This has two
  1131.  * relevant attributes:
  1132.  *
  1133.  * identity_url - The identity URL for which authentication was
  1134.  * attempted, if it can be determined.  Otherwise, null.
  1135.  *
  1136.  * status - Auth_OpenID_SUCCESS.
  1137.  *
  1138.  * @package OpenID
  1139.  */
  1140.     var $status = Auth_OpenID_CANCEL;
  1141.  
  1142.     function Auth_OpenID_CancelResponse($endpoint)
  1143.     {
  1144.         $this->endpoint $endpoint;
  1145.         $this->identity_url $endpoint->identity_url;
  1146.     }
  1147. }
  1148.  
  1149. /**
  1150.  * A response with a status of Auth_OpenID_SETUP_NEEDED. Indicates
  1151.  * that the request was in immediate mode, and the server is unable to
  1152.  * authenticate the user without further interaction.
  1153.  *
  1154.  * identity_url - The identity URL for which authentication was
  1155.  * attempted.
  1156.  *
  1157.  * setup_url - A URL that can be used to send the user to the server
  1158.  * to set up for authentication. The user should be redirected in to
  1159.  * the setup_url, either in the current window or in a new browser
  1160.  * window.
  1161.  *
  1162.  * status - Auth_OpenID_SETUP_NEEDED.
  1163.  *
  1164.  * @package OpenID
  1165.  */
  1166.     var $status = Auth_OpenID_SETUP_NEEDED;
  1167.  
  1168.     function Auth_OpenID_SetupNeededResponse($endpoint,
  1169.                                              $setup_url null)
  1170.     {
  1171.         $this->endpoint $endpoint;
  1172.         $this->identity_url $endpoint->identity_url;
  1173.         $this->setup_url $setup_url;
  1174.     }
  1175. }
  1176.  
  1177. ?>

Documentation generated on Mon, 05 Mar 2007 20:55:26 +0000 by phpDocumentor 1.3.1