web/Zend/OpenId/Consumer.php
changeset 0 4eba9c11703f
equal deleted inserted replaced
-1:000000000000 0:4eba9c11703f
       
     1 <?php
       
     2 
       
     3 /**
       
     4  * Zend Framework
       
     5  *
       
     6  * LICENSE
       
     7  *
       
     8  * This source file is subject to the new BSD license that is bundled
       
     9  * with this package in the file LICENSE.txt.
       
    10  * It is also available through the world-wide-web at this URL:
       
    11  * http://framework.zend.com/license/new-bsd
       
    12  * If you did not receive a copy of the license and are unable to
       
    13  * obtain it through the world-wide-web, please send an email
       
    14  * to license@zend.com so we can send you a copy immediately.
       
    15  *
       
    16  * @category   Zend
       
    17  * @package    Zend_OpenId
       
    18  * @subpackage Zend_OpenId_Consumer
       
    19  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    20  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    21  * @version    $Id: Consumer.php 20096 2010-01-06 02:05:09Z bkarwin $
       
    22  */
       
    23 
       
    24 /**
       
    25  * @see Zend_OpenId
       
    26  */
       
    27 require_once "Zend/OpenId.php";
       
    28 
       
    29 /**
       
    30  * @see Zend_OpenId_Extension
       
    31  */
       
    32 require_once "Zend/OpenId/Extension.php";
       
    33 
       
    34 /**
       
    35  * @see Zend_OpenId_Consumer_Storage
       
    36  */
       
    37 require_once "Zend/OpenId/Consumer/Storage.php";
       
    38 
       
    39 /**
       
    40  * @see Zend_Http_Client
       
    41  */
       
    42 require_once 'Zend/Http/Client.php';
       
    43 
       
    44 /**
       
    45  * OpenID consumer implementation
       
    46  *
       
    47  * @category   Zend
       
    48  * @package    Zend_OpenId
       
    49  * @subpackage Zend_OpenId_Consumer
       
    50  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    51  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    52  */
       
    53 class Zend_OpenId_Consumer
       
    54 {
       
    55 
       
    56     /**
       
    57      * Reference to an implementation of storage object
       
    58      *
       
    59      * @var Zend_OpenId_Consumer_Storage $_storage
       
    60      */
       
    61     protected $_storage = null;
       
    62 
       
    63     /**
       
    64      * Enables or disables consumer to use association with server based on
       
    65      * Diffie-Hellman key agreement
       
    66      *
       
    67      * @var Zend_OpenId_Consumer_Storage $_dumbMode
       
    68      */
       
    69     protected $_dumbMode = false;
       
    70 
       
    71     /**
       
    72      * Internal cache to prevent unnecessary access to storage
       
    73      *
       
    74      * @var array $_cache
       
    75      */
       
    76     protected $_cache = array();
       
    77 
       
    78     /**
       
    79      * HTTP client to make HTTP requests
       
    80      *
       
    81      * @var Zend_Http_Client $_httpClient
       
    82      */
       
    83     private $_httpClient = null;
       
    84 
       
    85     /**
       
    86      * HTTP session to store climed_id between requests
       
    87      *
       
    88      * @var Zend_Session_Namespace $_session
       
    89      */
       
    90     private $_session = null;
       
    91 
       
    92     /**
       
    93      * Last error message for logi, check or verify failure
       
    94      *
       
    95      * @var string $_error
       
    96      */
       
    97     private $_error = '';
       
    98 
       
    99     /**
       
   100      * Constructs a Zend_OpenId_Consumer object with given $storage.
       
   101      * Enables or disables future association with server based on
       
   102      * Diffie-Hellman key agreement.
       
   103      *
       
   104      * @param Zend_OpenId_Consumer_Storage $storage implementation of custom
       
   105      *  storage object
       
   106      * @param bool $dumbMode Enables or disables consumer to use association
       
   107      *  with server based on Diffie-Hellman key agreement
       
   108      */
       
   109     public function __construct(Zend_OpenId_Consumer_Storage $storage = null,
       
   110                                 $dumbMode = false)
       
   111     {
       
   112         if ($storage === null) {
       
   113             require_once "Zend/OpenId/Consumer/Storage/File.php";
       
   114             $this->_storage = new Zend_OpenId_Consumer_Storage_File();
       
   115         } else {
       
   116             $this->_storage = $storage;
       
   117         }
       
   118         $this->_dumbMode = $dumbMode;
       
   119     }
       
   120 
       
   121     /**
       
   122      * Performs check (with possible user interaction) of OpenID identity.
       
   123      *
       
   124      * This is the first step of OpenID authentication process.
       
   125      * On success the function does not return (it does HTTP redirection to
       
   126      * server and exits). On failure it returns false.
       
   127      *
       
   128      * @param string $id OpenID identity
       
   129      * @param string $returnTo URL to redirect response from server to
       
   130      * @param string $root HTTP URL to identify consumer on server
       
   131      * @param mixed $extensions extension object or array of extensions objects
       
   132      * @param Zend_Controller_Response_Abstract $response an optional response
       
   133      *  object to perform HTTP or HTML form redirection
       
   134      * @return bool
       
   135      */
       
   136     public function login($id, $returnTo = null, $root = null, $extensions = null,
       
   137                           Zend_Controller_Response_Abstract $response = null)
       
   138     {
       
   139         return $this->_checkId(
       
   140             false,
       
   141             $id,
       
   142             $returnTo,
       
   143             $root,
       
   144             $extensions,
       
   145             $response);
       
   146     }
       
   147 
       
   148     /**
       
   149      * Performs immediate check (without user interaction) of OpenID identity.
       
   150      *
       
   151      * This is the first step of OpenID authentication process.
       
   152      * On success the function does not return (it does HTTP redirection to
       
   153      * server and exits). On failure it returns false.
       
   154      *
       
   155      * @param string $id OpenID identity
       
   156      * @param string $returnTo HTTP URL to redirect response from server to
       
   157      * @param string $root HTTP URL to identify consumer on server
       
   158      * @param mixed $extensions extension object or array of extensions objects
       
   159      * @param Zend_Controller_Response_Abstract $response an optional response
       
   160      *  object to perform HTTP or HTML form redirection
       
   161      * @return bool
       
   162      */
       
   163     public function check($id, $returnTo=null, $root=null, $extensions = null,
       
   164                           Zend_Controller_Response_Abstract $response = null)
       
   165 
       
   166     {
       
   167         return $this->_checkId(
       
   168             true,
       
   169             $id,
       
   170             $returnTo,
       
   171             $root,
       
   172             $extensions,
       
   173             $response);
       
   174     }
       
   175 
       
   176     /**
       
   177      * Verifies authentication response from OpenID server.
       
   178      *
       
   179      * This is the second step of OpenID authentication process.
       
   180      * The function returns true on successful authentication and false on
       
   181      * failure.
       
   182      *
       
   183      * @param array $params HTTP query data from OpenID server
       
   184      * @param string &$identity this argument is set to end-user's claimed
       
   185      *  identifier or OpenID provider local identifier.
       
   186      * @param mixed $extensions extension object or array of extensions objects
       
   187      * @return bool
       
   188      */
       
   189     public function verify($params, &$identity = "", $extensions = null)
       
   190     {
       
   191         $this->_setError('');
       
   192 
       
   193         $version = 1.1;
       
   194         if (isset($params['openid_ns']) &&
       
   195             $params['openid_ns'] == Zend_OpenId::NS_2_0) {
       
   196             $version = 2.0;
       
   197         }
       
   198 
       
   199         if (isset($params["openid_claimed_id"])) {
       
   200             $identity = $params["openid_claimed_id"];
       
   201         } else if (isset($params["openid_identity"])){
       
   202             $identity = $params["openid_identity"];
       
   203         } else {
       
   204             $identity = "";
       
   205         }
       
   206 
       
   207         if ($version < 2.0 && !isset($params["openid_claimed_id"])) {
       
   208             if ($this->_session !== null) {
       
   209                 if ($this->_session->identity === $identity) {
       
   210                     $identity = $this->_session->claimed_id;
       
   211                 }
       
   212             } else if (defined('SID')) {
       
   213                 if (isset($_SESSION["zend_openid"]["identity"]) &&
       
   214                     isset($_SESSION["zend_openid"]["claimed_id"]) &&
       
   215                     $_SESSION["zend_openid"]["identity"] === $identity) {
       
   216                     $identity = $_SESSION["zend_openid"]["claimed_id"];
       
   217                 }
       
   218             } else {
       
   219                 require_once "Zend/Session/Namespace.php";
       
   220                 $this->_session = new Zend_Session_Namespace("zend_openid");
       
   221                 if ($this->_session->identity === $identity) {
       
   222                     $identity = $this->_session->claimed_id;
       
   223                 }
       
   224             }
       
   225         }
       
   226 
       
   227         if (empty($params['openid_mode'])) {
       
   228             $this->_setError("Missing openid.mode");
       
   229             return false;
       
   230         }
       
   231         if (empty($params['openid_return_to'])) {
       
   232             $this->_setError("Missing openid.return_to");
       
   233             return false;
       
   234         }
       
   235         if (empty($params['openid_signed'])) {
       
   236             $this->_setError("Missing openid.signed");
       
   237             return false;
       
   238         }
       
   239         if (empty($params['openid_sig'])) {
       
   240             $this->_setError("Missing openid.sig");
       
   241             return false;
       
   242         }
       
   243         if ($params['openid_mode'] != 'id_res') {
       
   244             $this->_setError("Wrong openid.mode '".$params['openid_mode']."' != 'id_res'");
       
   245             return false;
       
   246         }
       
   247         if (empty($params['openid_assoc_handle'])) {
       
   248             $this->_setError("Missing openid.assoc_handle");
       
   249             return false;
       
   250         }
       
   251         if ($params['openid_return_to'] != Zend_OpenId::selfUrl()) {
       
   252             /* Ignore query part in openid.return_to */
       
   253             $pos = strpos($params['openid_return_to'], '?');
       
   254             if ($pos === false ||
       
   255                 SUBSTR($params['openid_return_to'], 0 , $pos) != Zend_OpenId::selfUrl()) {
       
   256 
       
   257                 $this->_setError("Wrong openid.return_to '".
       
   258                     $params['openid_return_to']."' != '" . Zend_OpenId::selfUrl() ."'");
       
   259                 return false;
       
   260             }
       
   261         }
       
   262 
       
   263         if ($version >= 2.0) {
       
   264             if (empty($params['openid_response_nonce'])) {
       
   265                 $this->_setError("Missing openid.response_nonce");
       
   266                 return false;
       
   267             }
       
   268             if (empty($params['openid_op_endpoint'])) {
       
   269                 $this->_setError("Missing openid.op_endpoint");
       
   270                 return false;
       
   271             /* OpenID 2.0 (11.3) Checking the Nonce */
       
   272             } else if (!$this->_storage->isUniqueNonce($params['openid_op_endpoint'], $params['openid_response_nonce'])) {
       
   273                 $this->_setError("Duplicate openid.response_nonce");
       
   274                 return false;
       
   275             }
       
   276         }
       
   277 
       
   278 
       
   279         if (!empty($params['openid_invalidate_handle'])) {
       
   280             if ($this->_storage->getAssociationByHandle(
       
   281                 $params['openid_invalidate_handle'],
       
   282                 $url,
       
   283                 $macFunc,
       
   284                 $secret,
       
   285                 $expires)) {
       
   286                 $this->_storage->delAssociation($url);
       
   287             }
       
   288         }
       
   289 
       
   290         if ($this->_storage->getAssociationByHandle(
       
   291                 $params['openid_assoc_handle'],
       
   292                 $url,
       
   293                 $macFunc,
       
   294                 $secret,
       
   295                 $expires)) {
       
   296             $signed = explode(',', $params['openid_signed']);
       
   297             $data = '';
       
   298             foreach ($signed as $key) {
       
   299                 $data .= $key . ':' . $params['openid_' . strtr($key,'.','_')] . "\n";
       
   300             }
       
   301             if (base64_decode($params['openid_sig']) ==
       
   302                 Zend_OpenId::hashHmac($macFunc, $data, $secret)) {
       
   303                 if (!Zend_OpenId_Extension::forAll($extensions, 'parseResponse', $params)) {
       
   304                     $this->_setError("Extension::parseResponse failure");
       
   305                     return false;
       
   306                 }
       
   307                 /* OpenID 2.0 (11.2) Verifying Discovered Information */
       
   308                 if (isset($params['openid_claimed_id'])) {
       
   309                     $id = $params['openid_claimed_id'];
       
   310                     if (!Zend_OpenId::normalize($id)) {
       
   311                         $this->_setError("Normalization failed");
       
   312                         return false;
       
   313                     } else if (!$this->_discovery($id, $discovered_server, $discovered_version)) {
       
   314                         $this->_setError("Discovery failed: " . $this->getError());
       
   315                         return false;
       
   316                     } else if ((!empty($params['openid_identity']) &&
       
   317                                 $params["openid_identity"] != $id) ||
       
   318                                (!empty($params['openid_op_endpoint']) &&
       
   319                                 $params['openid_op_endpoint'] != $discovered_server) ||
       
   320                                $discovered_version != $version) {
       
   321                         $this->_setError("Discovery information verification failed");
       
   322                         return false;
       
   323                     }
       
   324                 }
       
   325                 return true;
       
   326             }
       
   327             $this->_storage->delAssociation($url);
       
   328             $this->_setError("Signature check failed");
       
   329             return false;
       
   330         }
       
   331         else
       
   332         {
       
   333             /* Use dumb mode */
       
   334             if (isset($params['openid_claimed_id'])) {
       
   335                 $id = $params['openid_claimed_id'];
       
   336             } else if (isset($params['openid_identity'])) {
       
   337                 $id = $params['openid_identity'];
       
   338             } else {
       
   339                 $this->_setError("Missing openid.claimed_id and openid.identity");
       
   340                 return false;
       
   341             }
       
   342 
       
   343             if (!Zend_OpenId::normalize($id)) {
       
   344                 $this->_setError("Normalization failed");
       
   345                 return false;
       
   346             } else if (!$this->_discovery($id, $server, $discovered_version)) {
       
   347                 $this->_setError("Discovery failed: " . $this->getError());
       
   348                 return false;
       
   349             }
       
   350 
       
   351             /* OpenID 2.0 (11.2) Verifying Discovered Information */
       
   352             if ((isset($params['openid_identity']) &&
       
   353                  $params["openid_identity"] != $id) ||
       
   354                 (isset($params['openid_op_endpoint']) &&
       
   355                  $params['openid_op_endpoint'] != $server) ||
       
   356                 $discovered_version != $version) {
       
   357                 $this->_setError("Discovery information verification failed");
       
   358                 return false;
       
   359             }
       
   360 
       
   361             $params2 = array();
       
   362             foreach ($params as $key => $val) {
       
   363                 if (strpos($key, 'openid_ns_') === 0) {
       
   364                     $key = 'openid.ns.' . substr($key, strlen('openid_ns_'));
       
   365                 } else if (strpos($key, 'openid_sreg_') === 0) {
       
   366                     $key = 'openid.sreg.' . substr($key, strlen('openid_sreg_'));
       
   367                 } else if (strpos($key, 'openid_') === 0) {
       
   368                     $key = 'openid.' . substr($key, strlen('openid_'));
       
   369                 }
       
   370                 $params2[$key] = $val;
       
   371             }
       
   372             $params2['openid.mode'] = 'check_authentication';
       
   373             $ret = $this->_httpRequest($server, 'POST', $params2, $status);
       
   374             if ($status != 200) {
       
   375                 $this->_setError("'Dumb' signature verification HTTP request failed");
       
   376                 return false;
       
   377             }
       
   378             $r = array();
       
   379             if (is_string($ret)) {
       
   380                 foreach(explode("\n", $ret) as $line) {
       
   381                     $line = trim($line);
       
   382                     if (!empty($line)) {
       
   383                         $x = explode(':', $line, 2);
       
   384                         if (is_array($x) && count($x) == 2) {
       
   385                             list($key, $value) = $x;
       
   386                             $r[trim($key)] = trim($value);
       
   387                         }
       
   388                     }
       
   389                 }
       
   390             }
       
   391             $ret = $r;
       
   392             if (!empty($ret['invalidate_handle'])) {
       
   393                 if ($this->_storage->getAssociationByHandle(
       
   394                     $ret['invalidate_handle'],
       
   395                     $url,
       
   396                     $macFunc,
       
   397                     $secret,
       
   398                     $expires)) {
       
   399                     $this->_storage->delAssociation($url);
       
   400                 }
       
   401             }
       
   402             if (isset($ret['is_valid']) && $ret['is_valid'] == 'true') {
       
   403                 if (!Zend_OpenId_Extension::forAll($extensions, 'parseResponse', $params)) {
       
   404                     $this->_setError("Extension::parseResponse failure");
       
   405                     return false;
       
   406                 }
       
   407                 return true;
       
   408             }
       
   409             $this->_setError("'Dumb' signature verification failed");
       
   410             return false;
       
   411         }
       
   412     }
       
   413 
       
   414     /**
       
   415      * Store assiciation in internal chace and external storage
       
   416      *
       
   417      * @param string $url OpenID server url
       
   418      * @param string $handle association handle
       
   419      * @param string $macFunc HMAC function (sha1 or sha256)
       
   420      * @param string $secret shared secret
       
   421      * @param integer $expires expiration UNIX time
       
   422      * @return void
       
   423      */
       
   424     protected function _addAssociation($url, $handle, $macFunc, $secret, $expires)
       
   425     {
       
   426         $this->_cache[$url] = array($handle, $macFunc, $secret, $expires);
       
   427         return $this->_storage->addAssociation(
       
   428             $url,
       
   429             $handle,
       
   430             $macFunc,
       
   431             $secret,
       
   432             $expires);
       
   433     }
       
   434 
       
   435     /**
       
   436      * Retrive assiciation information for given $url from internal cahce or
       
   437      * external storage
       
   438      *
       
   439      * @param string $url OpenID server url
       
   440      * @param string &$handle association handle
       
   441      * @param string &$macFunc HMAC function (sha1 or sha256)
       
   442      * @param string &$secret shared secret
       
   443      * @param integer &$expires expiration UNIX time
       
   444      * @return void
       
   445      */
       
   446     protected function _getAssociation($url, &$handle, &$macFunc, &$secret, &$expires)
       
   447     {
       
   448         if (isset($this->_cache[$url])) {
       
   449             $handle   = $this->_cache[$url][0];
       
   450             $macFunc = $this->_cache[$url][1];
       
   451             $secret   = $this->_cache[$url][2];
       
   452             $expires  = $this->_cache[$url][3];
       
   453             return true;
       
   454         }
       
   455         if ($this->_storage->getAssociation(
       
   456                 $url,
       
   457                 $handle,
       
   458                 $macFunc,
       
   459                 $secret,
       
   460                 $expires)) {
       
   461             $this->_cache[$url] = array($handle, $macFunc, $secret, $expires);
       
   462             return true;
       
   463         }
       
   464         return false;
       
   465     }
       
   466 
       
   467     /**
       
   468      * Performs HTTP request to given $url using given HTTP $method.
       
   469      * Send additinal query specified by variable/value array,
       
   470      * On success returns HTTP response without headers, false on failure.
       
   471      *
       
   472      * @param string $url OpenID server url
       
   473      * @param string $method HTTP request method 'GET' or 'POST'
       
   474      * @param array $params additional qwery parameters to be passed with
       
   475      * @param int &$staus HTTP status code
       
   476      *  request
       
   477      * @return mixed
       
   478      */
       
   479     protected function _httpRequest($url, $method = 'GET', array $params = array(), &$status = null)
       
   480     {
       
   481         $client = $this->_httpClient;
       
   482         if ($client === null) {
       
   483             $client = new Zend_Http_Client(
       
   484                     $url,
       
   485                     array(
       
   486                         'maxredirects' => 4,
       
   487                         'timeout'      => 15,
       
   488                         'useragent'    => 'Zend_OpenId'
       
   489                     )
       
   490                 );
       
   491         } else {
       
   492             $client->setUri($url);
       
   493         }
       
   494 
       
   495         $client->resetParameters();
       
   496         if ($method == 'POST') {
       
   497             $client->setMethod(Zend_Http_Client::POST);
       
   498             $client->setParameterPost($params);
       
   499         } else {
       
   500             $client->setMethod(Zend_Http_Client::GET);
       
   501             $client->setParameterGet($params);
       
   502         }
       
   503 
       
   504         try {
       
   505             $response = $client->request();
       
   506         } catch (Exception $e) {
       
   507             $this->_setError('HTTP Request failed: ' . $e->getMessage());
       
   508             return false;
       
   509         }
       
   510         $status = $response->getStatus();
       
   511         $body = $response->getBody();
       
   512         if ($status == 200 || ($status == 400 && !empty($body))) {
       
   513             return $body;
       
   514         }else{
       
   515             $this->_setError('Bad HTTP response');
       
   516             return false;
       
   517         }
       
   518     }
       
   519 
       
   520     /**
       
   521      * Create (or reuse existing) association between OpenID consumer and
       
   522      * OpenID server based on Diffie-Hellman key agreement. Returns true
       
   523      * on success and false on failure.
       
   524      *
       
   525      * @param string $url OpenID server url
       
   526      * @param float $version OpenID protocol version
       
   527      * @param string $priv_key for testing only
       
   528      * @return bool
       
   529      */
       
   530     protected function _associate($url, $version, $priv_key=null)
       
   531     {
       
   532 
       
   533         /* Check if we already have association in chace or storage */
       
   534         if ($this->_getAssociation(
       
   535                 $url,
       
   536                 $handle,
       
   537                 $macFunc,
       
   538                 $secret,
       
   539                 $expires)) {
       
   540             return true;
       
   541         }
       
   542 
       
   543         if ($this->_dumbMode) {
       
   544             /* Use dumb mode */
       
   545             return true;
       
   546         }
       
   547 
       
   548         $params = array();
       
   549 
       
   550         if ($version >= 2.0) {
       
   551             $params = array(
       
   552                 'openid.ns'           => Zend_OpenId::NS_2_0,
       
   553                 'openid.mode'         => 'associate',
       
   554                 'openid.assoc_type'   => 'HMAC-SHA256',
       
   555                 'openid.session_type' => 'DH-SHA256',
       
   556             );
       
   557         } else {
       
   558             $params = array(
       
   559                 'openid.mode'         => 'associate',
       
   560                 'openid.assoc_type'   => 'HMAC-SHA1',
       
   561                 'openid.session_type' => 'DH-SHA1',
       
   562             );
       
   563         }
       
   564 
       
   565         $dh = Zend_OpenId::createDhKey(pack('H*', Zend_OpenId::DH_P),
       
   566                                        pack('H*', Zend_OpenId::DH_G),
       
   567                                        $priv_key);
       
   568         $dh_details = Zend_OpenId::getDhKeyDetails($dh);
       
   569 
       
   570         $params['openid.dh_modulus']         = base64_encode(
       
   571             Zend_OpenId::btwoc($dh_details['p']));
       
   572         $params['openid.dh_gen']             = base64_encode(
       
   573             Zend_OpenId::btwoc($dh_details['g']));
       
   574         $params['openid.dh_consumer_public'] = base64_encode(
       
   575             Zend_OpenId::btwoc($dh_details['pub_key']));
       
   576 
       
   577         while(1) {
       
   578             $ret = $this->_httpRequest($url, 'POST', $params, $status);
       
   579             if ($ret === false) {
       
   580                 $this->_setError("HTTP request failed");
       
   581                 return false;
       
   582             }
       
   583 
       
   584             $r = array();
       
   585             $bad_response = false;
       
   586             foreach(explode("\n", $ret) as $line) {
       
   587                 $line = trim($line);
       
   588                 if (!empty($line)) {
       
   589                     $x = explode(':', $line, 2);
       
   590                     if (is_array($x) && count($x) == 2) {
       
   591                         list($key, $value) = $x;
       
   592                         $r[trim($key)] = trim($value);
       
   593                     } else {
       
   594                         $bad_response = true;
       
   595                     }
       
   596                 }
       
   597             }
       
   598             if ($bad_response && strpos($ret, 'Unknown session type') !== false) {
       
   599                 $r['error_code'] = 'unsupported-type';
       
   600             }
       
   601             $ret = $r;
       
   602 
       
   603             if (isset($ret['error_code']) &&
       
   604                 $ret['error_code'] == 'unsupported-type') {
       
   605                 if ($params['openid.session_type'] == 'DH-SHA256') {
       
   606                     $params['openid.session_type'] = 'DH-SHA1';
       
   607                     $params['openid.assoc_type'] = 'HMAC-SHA1';
       
   608                 } else if ($params['openid.session_type'] == 'DH-SHA1') {
       
   609                     $params['openid.session_type'] = 'no-encryption';
       
   610                 } else {
       
   611                     $this->_setError("The OpenID service responded with: " . $ret['error_code']);
       
   612                     return false;
       
   613                 }
       
   614             } else {
       
   615                 break;
       
   616             }
       
   617         }
       
   618 
       
   619         if ($status != 200) {
       
   620             $this->_setError("The server responded with status code: " . $status);
       
   621             return false;
       
   622         }
       
   623 
       
   624         if ($version >= 2.0 &&
       
   625             isset($ret['ns']) &&
       
   626             $ret['ns'] != Zend_OpenId::NS_2_0) {
       
   627             $this->_setError("Wrong namespace definition in the server response");
       
   628             return false;
       
   629         }
       
   630 
       
   631         if (!isset($ret['assoc_handle']) ||
       
   632             !isset($ret['expires_in']) ||
       
   633             !isset($ret['assoc_type']) ||
       
   634             $params['openid.assoc_type'] != $ret['assoc_type']) {
       
   635             if ($params['openid.assoc_type'] != $ret['assoc_type']) {
       
   636                 $this->_setError("The returned assoc_type differed from the supplied openid.assoc_type");
       
   637             } else {
       
   638                 $this->_setError("Missing required data from provider (assoc_handle, expires_in, assoc_type are required)");
       
   639             }
       
   640             return false;
       
   641         }
       
   642 
       
   643         $handle     = $ret['assoc_handle'];
       
   644         $expiresIn = $ret['expires_in'];
       
   645 
       
   646         if ($ret['assoc_type'] == 'HMAC-SHA1') {
       
   647             $macFunc = 'sha1';
       
   648         } else if ($ret['assoc_type'] == 'HMAC-SHA256' &&
       
   649             $version >= 2.0) {
       
   650             $macFunc = 'sha256';
       
   651         } else {
       
   652             $this->_setError("Unsupported assoc_type");
       
   653             return false;
       
   654         }
       
   655 
       
   656         if ((empty($ret['session_type']) ||
       
   657              ($version >= 2.0 && $ret['session_type'] == 'no-encryption')) &&
       
   658              isset($ret['mac_key'])) {
       
   659             $secret = base64_decode($ret['mac_key']);
       
   660         } else if (isset($ret['session_type']) &&
       
   661             $ret['session_type'] == 'DH-SHA1' &&
       
   662             !empty($ret['dh_server_public']) &&
       
   663             !empty($ret['enc_mac_key'])) {
       
   664             $dhFunc = 'sha1';
       
   665         } else if (isset($ret['session_type']) &&
       
   666             $ret['session_type'] == 'DH-SHA256' &&
       
   667             $version >= 2.0 &&
       
   668             !empty($ret['dh_server_public']) &&
       
   669             !empty($ret['enc_mac_key'])) {
       
   670             $dhFunc = 'sha256';
       
   671         } else {
       
   672             $this->_setError("Unsupported session_type");
       
   673             return false;
       
   674         }
       
   675         if (isset($dhFunc)) {
       
   676             $serverPub = base64_decode($ret['dh_server_public']);
       
   677             $dhSec = Zend_OpenId::computeDhSecret($serverPub, $dh);
       
   678             if ($dhSec === false) {
       
   679                 $this->_setError("DH secret comutation failed");
       
   680                 return false;
       
   681             }
       
   682             $sec = Zend_OpenId::digest($dhFunc, $dhSec);
       
   683             if ($sec === false) {
       
   684                 $this->_setError("Could not create digest");
       
   685                 return false;
       
   686             }
       
   687             $secret = $sec ^ base64_decode($ret['enc_mac_key']);
       
   688         }
       
   689         if ($macFunc == 'sha1') {
       
   690             if (Zend_OpenId::strlen($secret) != 20) {
       
   691                 $this->_setError("The length of the sha1 secret must be 20");
       
   692                 return false;
       
   693             }
       
   694         } else if ($macFunc == 'sha256') {
       
   695             if (Zend_OpenId::strlen($secret) != 32) {
       
   696                 $this->_setError("The length of the sha256 secret must be 32");
       
   697                 return false;
       
   698             }
       
   699         }
       
   700         $this->_addAssociation(
       
   701             $url,
       
   702             $handle,
       
   703             $macFunc,
       
   704             $secret,
       
   705             time() + $expiresIn);
       
   706         return true;
       
   707     }
       
   708 
       
   709     /**
       
   710      * Performs discovery of identity and finds OpenID URL, OpenID server URL
       
   711      * and OpenID protocol version. Returns true on succees and false on
       
   712      * failure.
       
   713      *
       
   714      * @param string &$id OpenID identity URL
       
   715      * @param string &$server OpenID server URL
       
   716      * @param float &$version OpenID protocol version
       
   717      * @return bool
       
   718      * @todo OpenID 2.0 (7.3) XRI and Yadis discovery
       
   719      */
       
   720     protected function _discovery(&$id, &$server, &$version)
       
   721     {
       
   722         $realId = $id;
       
   723         if ($this->_storage->getDiscoveryInfo(
       
   724                 $id,
       
   725                 $realId,
       
   726                 $server,
       
   727                 $version,
       
   728                 $expire)) {
       
   729             $id = $realId;
       
   730             return true;
       
   731         }
       
   732 
       
   733         /* TODO: OpenID 2.0 (7.3) XRI and Yadis discovery */
       
   734 
       
   735         /* HTML-based discovery */
       
   736         $response = $this->_httpRequest($id, 'GET', array(), $status);
       
   737         if ($status != 200 || !is_string($response)) {
       
   738             return false;
       
   739         }
       
   740         if (preg_match(
       
   741                 '/<link[^>]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid2.provider[ \t]*[^"\']*\\1[^>]*href=(["\'])([^"\']+)\\2[^>]*\/?>/i',
       
   742                 $response,
       
   743                 $r)) {
       
   744             $version = 2.0;
       
   745             $server = $r[3];
       
   746         } else if (preg_match(
       
   747                 '/<link[^>]*href=(["\'])([^"\']+)\\1[^>]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid2.provider[ \t]*[^"\']*\\3[^>]*\/?>/i',
       
   748                 $response,
       
   749                 $r)) {
       
   750             $version = 2.0;
       
   751             $server = $r[2];
       
   752         } else if (preg_match(
       
   753                 '/<link[^>]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid.server[ \t]*[^"\']*\\1[^>]*href=(["\'])([^"\']+)\\2[^>]*\/?>/i',
       
   754                 $response,
       
   755                 $r)) {
       
   756             $version = 1.1;
       
   757             $server = $r[3];
       
   758         } else if (preg_match(
       
   759                 '/<link[^>]*href=(["\'])([^"\']+)\\1[^>]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid.server[ \t]*[^"\']*\\3[^>]*\/?>/i',
       
   760                 $response,
       
   761                 $r)) {
       
   762             $version = 1.1;
       
   763             $server = $r[2];
       
   764         } else {
       
   765             return false;
       
   766         }
       
   767         if ($version >= 2.0) {
       
   768             if (preg_match(
       
   769                     '/<link[^>]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid2.local_id[ \t]*[^"\']*\\1[^>]*href=(["\'])([^"\']+)\\2[^>]*\/?>/i',
       
   770                     $response,
       
   771                     $r)) {
       
   772                 $realId = $r[3];
       
   773             } else if (preg_match(
       
   774                     '/<link[^>]*href=(["\'])([^"\']+)\\1[^>]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid2.local_id[ \t]*[^"\']*\\3[^>]*\/?>/i',
       
   775                     $response,
       
   776                     $r)) {
       
   777                 $realId = $r[2];
       
   778             }
       
   779         } else {
       
   780             if (preg_match(
       
   781                     '/<link[^>]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid.delegate[ \t]*[^"\']*\\1[^>]*href=(["\'])([^"\']+)\\2[^>]*\/?>/i',
       
   782                     $response,
       
   783                     $r)) {
       
   784                 $realId = $r[3];
       
   785             } else if (preg_match(
       
   786                     '/<link[^>]*href=(["\'])([^"\']+)\\1[^>]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid.delegate[ \t]*[^"\']*\\3[^>]*\/?>/i',
       
   787                     $response,
       
   788                     $r)) {
       
   789                 $realId = $r[2];
       
   790             }
       
   791         }
       
   792 
       
   793         $expire = time() + 60 * 60;
       
   794         $this->_storage->addDiscoveryInfo($id, $realId, $server, $version, $expire);
       
   795         $id = $realId;
       
   796         return true;
       
   797     }
       
   798 
       
   799     /**
       
   800      * Performs check of OpenID identity.
       
   801      *
       
   802      * This is the first step of OpenID authentication process.
       
   803      * On success the function does not return (it does HTTP redirection to
       
   804      * server and exits). On failure it returns false.
       
   805      *
       
   806      * @param bool $immediate enables or disables interaction with user
       
   807      * @param string $id OpenID identity
       
   808      * @param string $returnTo HTTP URL to redirect response from server to
       
   809      * @param string $root HTTP URL to identify consumer on server
       
   810      * @param mixed $extensions extension object or array of extensions objects
       
   811      * @param Zend_Controller_Response_Abstract $response an optional response
       
   812      *  object to perform HTTP or HTML form redirection
       
   813      * @return bool
       
   814      */
       
   815     protected function _checkId($immediate, $id, $returnTo=null, $root=null,
       
   816         $extensions=null, Zend_Controller_Response_Abstract $response = null)
       
   817     {
       
   818         $this->_setError('');
       
   819 
       
   820         if (!Zend_OpenId::normalize($id)) {
       
   821             $this->_setError("Normalisation failed");
       
   822             return false;
       
   823         }
       
   824         $claimedId = $id;
       
   825 
       
   826         if (!$this->_discovery($id, $server, $version)) {
       
   827             $this->_setError("Discovery failed: " . $this->getError());
       
   828             return false;
       
   829         }
       
   830         if (!$this->_associate($server, $version)) {
       
   831             $this->_setError("Association failed: " . $this->getError());
       
   832             return false;
       
   833         }
       
   834         if (!$this->_getAssociation(
       
   835                 $server,
       
   836                 $handle,
       
   837                 $macFunc,
       
   838                 $secret,
       
   839                 $expires)) {
       
   840             /* Use dumb mode */
       
   841             unset($handle);
       
   842             unset($macFunc);
       
   843             unset($secret);
       
   844             unset($expires);
       
   845         }
       
   846 
       
   847         $params = array();
       
   848         if ($version >= 2.0) {
       
   849             $params['openid.ns'] = Zend_OpenId::NS_2_0;
       
   850         }
       
   851 
       
   852         $params['openid.mode'] = $immediate ?
       
   853             'checkid_immediate' : 'checkid_setup';
       
   854 
       
   855         $params['openid.identity'] = $id;
       
   856 
       
   857         $params['openid.claimed_id'] = $claimedId;
       
   858 
       
   859         if ($version <= 2.0) {
       
   860             if ($this->_session !== null) {
       
   861                 $this->_session->identity = $id;
       
   862                 $this->_session->claimed_id = $claimedId;
       
   863             } else if (defined('SID')) {
       
   864                 $_SESSION["zend_openid"] = array(
       
   865                     "identity" => $id,
       
   866                     "claimed_id" => $claimedId);
       
   867             } else {
       
   868                 require_once "Zend/Session/Namespace.php";
       
   869                 $this->_session = new Zend_Session_Namespace("zend_openid");
       
   870                 $this->_session->identity = $id;
       
   871                 $this->_session->claimed_id = $claimedId;
       
   872             }
       
   873         }
       
   874 
       
   875         if (isset($handle)) {
       
   876             $params['openid.assoc_handle'] = $handle;
       
   877         }
       
   878 
       
   879         $params['openid.return_to'] = Zend_OpenId::absoluteUrl($returnTo);
       
   880 
       
   881         if (empty($root)) {
       
   882             $root = Zend_OpenId::selfUrl();
       
   883             if ($root[strlen($root)-1] != '/') {
       
   884                 $root = dirname($root);
       
   885             }
       
   886         }
       
   887         if ($version >= 2.0) {
       
   888             $params['openid.realm'] = $root;
       
   889         } else {
       
   890             $params['openid.trust_root'] = $root;
       
   891         }
       
   892 
       
   893         if (!Zend_OpenId_Extension::forAll($extensions, 'prepareRequest', $params)) {
       
   894             $this->_setError("Extension::prepareRequest failure");
       
   895             return false;
       
   896         }
       
   897 
       
   898         Zend_OpenId::redirect($server, $params, $response);
       
   899         return true;
       
   900     }
       
   901 
       
   902     /**
       
   903      * Sets HTTP client object to make HTTP requests
       
   904      *
       
   905      * @param Zend_Http_Client $client HTTP client object to be used
       
   906      */
       
   907     public function setHttpClient($client) {
       
   908         $this->_httpClient = $client;
       
   909     }
       
   910 
       
   911     /**
       
   912      * Returns HTTP client object that will be used to make HTTP requests
       
   913      *
       
   914      * @return Zend_Http_Client
       
   915      */
       
   916     public function getHttpClient() {
       
   917         return $this->_httpClient;
       
   918     }
       
   919 
       
   920     /**
       
   921      * Sets session object to store climed_id
       
   922      *
       
   923      * @param Zend_Session_Namespace $session HTTP client object to be used
       
   924      */
       
   925     public function setSession(Zend_Session_Namespace $session) {
       
   926         $this->_session = $session;
       
   927     }
       
   928 
       
   929     /**
       
   930      * Returns session object that is used to store climed_id
       
   931      *
       
   932      * @return Zend_Session_Namespace
       
   933      */
       
   934     public function getSession() {
       
   935         return $this->_session;
       
   936     }
       
   937 
       
   938     /**
       
   939      * Saves error message
       
   940      *
       
   941      * @param string $message error message
       
   942      */
       
   943     protected function _setError($message)
       
   944     {
       
   945         $this->_error = $message;
       
   946     }
       
   947 
       
   948     /**
       
   949      * Returns error message that explains failure of login, check or verify
       
   950      *
       
   951      * @return string
       
   952      */
       
   953     public function getError()
       
   954     {
       
   955         return $this->_error;
       
   956     }
       
   957 
       
   958 }