web/lib/Zend/OpenId/Provider.php
changeset 64 162c1de6545a
parent 19 1c2f13fd785c
child 68 ecaf28ffe26e
equal deleted inserted replaced
63:5b37998e522e 64:162c1de6545a
       
     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_Provider
       
    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: Provider.php 23088 2010-10-11 19:53:24Z padraic $
       
    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  * OpenID provider (server) implementation
       
    36  *
       
    37  * @category   Zend
       
    38  * @package    Zend_OpenId
       
    39  * @subpackage Zend_OpenId_Provider
       
    40  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    41  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    42  */
       
    43 class Zend_OpenId_Provider
       
    44 {
       
    45 
       
    46     /**
       
    47      * Reference to an implementation of storage object
       
    48      *
       
    49      * @var Zend_OpenId_Provider_Storage $_storage
       
    50      */
       
    51     private $_storage;
       
    52 
       
    53     /**
       
    54      * Reference to an implementation of user object
       
    55      *
       
    56      * @var Zend_OpenId_Provider_User $_user
       
    57      */
       
    58     private $_user;
       
    59 
       
    60     /**
       
    61      * Time to live of association session in secconds
       
    62      *
       
    63      * @var integer $_sessionTtl
       
    64      */
       
    65     private $_sessionTtl;
       
    66 
       
    67     /**
       
    68      * URL to peform interactive user login
       
    69      *
       
    70      * @var string $_loginUrl
       
    71      */
       
    72     private $_loginUrl;
       
    73 
       
    74     /**
       
    75      * URL to peform interactive validation of consumer by user
       
    76      *
       
    77      * @var string $_trustUrl
       
    78      */
       
    79     private $_trustUrl;
       
    80 
       
    81     /**
       
    82      * The OP Endpoint URL
       
    83      *
       
    84      * @var string $_opEndpoint
       
    85      */
       
    86     private $_opEndpoint;
       
    87 
       
    88     /**
       
    89      * Constructs a Zend_OpenId_Provider object with given parameters.
       
    90      *
       
    91      * @param string $loginUrl is an URL that provides login screen for
       
    92      *  end-user (by default it is the same URL with additional GET variable
       
    93      *  openid.action=login)
       
    94      * @param string $trustUrl is an URL that shows a question if end-user
       
    95      *  trust to given consumer (by default it is the same URL with additional
       
    96      *  GET variable openid.action=trust)
       
    97      * @param Zend_OpenId_Provider_User $user is an object for communication
       
    98      *  with User-Agent and store information about logged-in user (it is a
       
    99      *  Zend_OpenId_Provider_User_Session object by default)
       
   100      * @param Zend_OpenId_Provider_Storage $storage is an object for keeping
       
   101      *  persistent database (it is a Zend_OpenId_Provider_Storage_File object
       
   102      *  by default)
       
   103      * @param integer $sessionTtl is a default time to live for association
       
   104      *   session in seconds (1 hour by default). Consumer must reestablish
       
   105      *   association after that time.
       
   106      */
       
   107     public function __construct($loginUrl = null,
       
   108                                 $trustUrl = null,
       
   109                                 Zend_OpenId_Provider_User $user = null,
       
   110                                 Zend_OpenId_Provider_Storage $storage = null,
       
   111                                 $sessionTtl = 3600)
       
   112     {
       
   113         if ($loginUrl === null) {
       
   114             $loginUrl = Zend_OpenId::selfUrl() . '?openid.action=login';
       
   115         } else {
       
   116             $loginUrl = Zend_OpenId::absoluteUrl($loginUrl);
       
   117         }
       
   118         $this->_loginUrl = $loginUrl;
       
   119         if ($trustUrl === null) {
       
   120             $trustUrl = Zend_OpenId::selfUrl() . '?openid.action=trust';
       
   121         } else {
       
   122             $trustUrl = Zend_OpenId::absoluteUrl($trustUrl);
       
   123         }
       
   124         $this->_trustUrl = $trustUrl;
       
   125         if ($user === null) {
       
   126             require_once "Zend/OpenId/Provider/User/Session.php";
       
   127             $this->_user = new Zend_OpenId_Provider_User_Session();
       
   128         } else {
       
   129             $this->_user = $user;
       
   130         }
       
   131         if ($storage === null) {
       
   132             require_once "Zend/OpenId/Provider/Storage/File.php";
       
   133             $this->_storage = new Zend_OpenId_Provider_Storage_File();
       
   134         } else {
       
   135             $this->_storage = $storage;
       
   136         }
       
   137         $this->_sessionTtl = $sessionTtl;
       
   138     }
       
   139 
       
   140     /**
       
   141      * Sets the OP Endpoint URL
       
   142      *
       
   143      * @param string $url the OP Endpoint URL
       
   144      * @return null
       
   145      */
       
   146     public function setOpEndpoint($url)
       
   147     {
       
   148         $this->_opEndpoint = $url;
       
   149     }
       
   150 
       
   151     /**
       
   152      * Registers a new user with given $id and $password
       
   153      * Returns true in case of success and false if user with given $id already
       
   154      * exists
       
   155      *
       
   156      * @param string $id user identity URL
       
   157      * @param string $password encoded user password
       
   158      * @return bool
       
   159      */
       
   160     public function register($id, $password)
       
   161     {
       
   162         if (!Zend_OpenId::normalize($id) || empty($id)) {
       
   163             return false;
       
   164         }
       
   165         return $this->_storage->addUser($id, md5($id.$password));
       
   166     }
       
   167 
       
   168     /**
       
   169      * Returns true if user with given $id exists and false otherwise
       
   170      *
       
   171      * @param string $id user identity URL
       
   172      * @return bool
       
   173      */
       
   174     public function hasUser($id) {
       
   175         if (!Zend_OpenId::normalize($id)) {
       
   176             return false;
       
   177         }
       
   178         return $this->_storage->hasUser($id);
       
   179     }
       
   180 
       
   181     /**
       
   182      * Performs login of user with given $id and $password
       
   183      * Returns true in case of success and false otherwise
       
   184      *
       
   185      * @param string $id user identity URL
       
   186      * @param string $password user password
       
   187      * @return bool
       
   188      */
       
   189     public function login($id, $password)
       
   190     {
       
   191         if (!Zend_OpenId::normalize($id)) {
       
   192             return false;
       
   193         }
       
   194         if (!$this->_storage->checkUser($id, md5($id.$password))) {
       
   195             return false;
       
   196         }
       
   197         $this->_user->setLoggedInUser($id);
       
   198         return true;
       
   199     }
       
   200 
       
   201     /**
       
   202      * Performs logout. Clears information about logged in user.
       
   203      *
       
   204      * @return void
       
   205      */
       
   206     public function logout()
       
   207     {
       
   208         $this->_user->delLoggedInUser();
       
   209         return true;
       
   210     }
       
   211 
       
   212     /**
       
   213      * Returns identity URL of current logged in user or false
       
   214      *
       
   215      * @return mixed
       
   216      */
       
   217     public function getLoggedInUser() {
       
   218         return $this->_user->getLoggedInUser();
       
   219     }
       
   220 
       
   221     /**
       
   222      * Retrieve consumer's root URL from request query.
       
   223      * Returns URL or false in case of failure
       
   224      *
       
   225      * @param array $params query arguments
       
   226      * @return mixed
       
   227      */
       
   228     public function getSiteRoot($params)
       
   229     {
       
   230         $version = 1.1;
       
   231         if (isset($params['openid_ns']) &&
       
   232             $params['openid_ns'] == Zend_OpenId::NS_2_0) {
       
   233             $version = 2.0;
       
   234         }
       
   235         if ($version >= 2.0 && isset($params['openid_realm'])) {
       
   236             $root = $params['openid_realm'];
       
   237         } else if ($version < 2.0 && isset($params['openid_trust_root'])) {
       
   238             $root = $params['openid_trust_root'];
       
   239         } else if (isset($params['openid_return_to'])) {
       
   240             $root = $params['openid_return_to'];
       
   241         } else {
       
   242             return false;
       
   243         }
       
   244         if (Zend_OpenId::normalizeUrl($root) && !empty($root)) {
       
   245             return $root;
       
   246         }
       
   247         return false;
       
   248     }
       
   249 
       
   250     /**
       
   251      * Allows consumer with given root URL to authenticate current logged
       
   252      * in user. Returns true on success and false on error.
       
   253      *
       
   254      * @param string $root root URL
       
   255      * @param mixed $extensions extension object or array of extensions objects
       
   256      * @return bool
       
   257      */
       
   258     public function allowSite($root, $extensions=null)
       
   259     {
       
   260         $id = $this->getLoggedInUser();
       
   261         if ($id === false) {
       
   262             return false;
       
   263         }
       
   264         if ($extensions !== null) {
       
   265             $data = array();
       
   266             Zend_OpenId_Extension::forAll($extensions, 'getTrustData', $data);
       
   267         } else {
       
   268             $data = true;
       
   269         }
       
   270         $this->_storage->addSite($id, $root, $data);
       
   271         return true;
       
   272     }
       
   273 
       
   274     /**
       
   275      * Prohibit consumer with given root URL to authenticate current logged
       
   276      * in user. Returns true on success and false on error.
       
   277      *
       
   278      * @param string $root root URL
       
   279      * @return bool
       
   280      */
       
   281     public function denySite($root)
       
   282     {
       
   283         $id = $this->getLoggedInUser();
       
   284         if ($id === false) {
       
   285             return false;
       
   286         }
       
   287         $this->_storage->addSite($id, $root, false);
       
   288         return true;
       
   289     }
       
   290 
       
   291     /**
       
   292      * Delete consumer with given root URL from known sites of current logged
       
   293      * in user. Next time this consumer will try to authenticate the user,
       
   294      * Provider will ask user's confirmation.
       
   295      * Returns true on success and false on error.
       
   296      *
       
   297      * @param string $root root URL
       
   298      * @return bool
       
   299      */
       
   300     public function delSite($root)
       
   301     {
       
   302         $id = $this->getLoggedInUser();
       
   303         if ($id === false) {
       
   304             return false;
       
   305         }
       
   306         $this->_storage->addSite($id, $root, null);
       
   307         return true;
       
   308     }
       
   309 
       
   310     /**
       
   311      * Returns list of known consumers for current logged in user or false
       
   312      * if he is not logged in.
       
   313      *
       
   314      * @return mixed
       
   315      */
       
   316     public function getTrustedSites()
       
   317     {
       
   318         $id = $this->getLoggedInUser();
       
   319         if ($id === false) {
       
   320             return false;
       
   321         }
       
   322         return $this->_storage->getTrustedSites($id);
       
   323     }
       
   324 
       
   325     /**
       
   326      * Handles HTTP request from consumer
       
   327      *
       
   328      * @param array $params GET or POST variables. If this parameter is omited
       
   329      *  or set to null, then $_GET or $_POST superglobal variable is used
       
   330      *  according to REQUEST_METHOD.
       
   331      * @param mixed $extensions extension object or array of extensions objects
       
   332      * @param Zend_Controller_Response_Abstract $response an optional response
       
   333      *  object to perform HTTP or HTML form redirection
       
   334      * @return mixed
       
   335      */
       
   336     public function handle($params=null, $extensions=null,
       
   337                            Zend_Controller_Response_Abstract $response = null)
       
   338     {
       
   339         if ($params === null) {
       
   340             if ($_SERVER["REQUEST_METHOD"] == "GET") {
       
   341                 $params = $_GET;
       
   342             } else if ($_SERVER["REQUEST_METHOD"] == "POST") {
       
   343                 $params = $_POST;
       
   344             } else {
       
   345                 return false;
       
   346             }
       
   347         }
       
   348         $version = 1.1;
       
   349         if (isset($params['openid_ns']) &&
       
   350             $params['openid_ns'] == Zend_OpenId::NS_2_0) {
       
   351             $version = 2.0;
       
   352         }
       
   353         if (isset($params['openid_mode'])) {
       
   354             if ($params['openid_mode'] == 'associate') {
       
   355                 $response = $this->_associate($version, $params);
       
   356                 $ret = '';
       
   357                 foreach ($response as $key => $val) {
       
   358                     $ret .= $key . ':' . $val . "\n";
       
   359                 }
       
   360                 return $ret;
       
   361             } else if ($params['openid_mode'] == 'checkid_immediate') {
       
   362                 $ret = $this->_checkId($version, $params, 1, $extensions, $response);
       
   363                 if (is_bool($ret)) return $ret;
       
   364                 if (!empty($params['openid_return_to'])) {
       
   365                     Zend_OpenId::redirect($params['openid_return_to'], $ret, $response);
       
   366                 }
       
   367                 return true;
       
   368             } else if ($params['openid_mode'] == 'checkid_setup') {
       
   369                 $ret = $this->_checkId($version, $params, 0, $extensions, $response);
       
   370                 if (is_bool($ret)) return $ret;
       
   371                 if (!empty($params['openid_return_to'])) {
       
   372                     Zend_OpenId::redirect($params['openid_return_to'], $ret, $response);
       
   373                 }
       
   374                 return true;
       
   375             } else if ($params['openid_mode'] == 'check_authentication') {
       
   376                 $response = $this->_checkAuthentication($version, $params);
       
   377                 $ret = '';
       
   378                 foreach ($response as $key => $val) {
       
   379                     $ret .= $key . ':' . $val . "\n";
       
   380                 }
       
   381                 return $ret;
       
   382             }
       
   383         }
       
   384         return false;
       
   385     }
       
   386 
       
   387     /**
       
   388      * Generates a secret key for given hash function, returns RAW key or false
       
   389      * if function is not supported
       
   390      *
       
   391      * @param string $func hash function (sha1 or sha256)
       
   392      * @return mixed
       
   393      */
       
   394     protected function _genSecret($func)
       
   395     {
       
   396         if ($func == 'sha1') {
       
   397             $macLen = 20; /* 160 bit */
       
   398         } else if ($func == 'sha256') {
       
   399             $macLen = 32; /* 256 bit */
       
   400         } else {
       
   401             return false;
       
   402         }
       
   403         return Zend_OpenId::randomBytes($macLen);
       
   404     }
       
   405 
       
   406     /**
       
   407      * Processes association request from OpenID consumerm generates secret
       
   408      * shared key and send it back using Diffie-Hellman encruption.
       
   409      * Returns array of variables to push back to consumer.
       
   410      *
       
   411      * @param float $version OpenID version
       
   412      * @param array $params GET or POST request variables
       
   413      * @return array
       
   414      */
       
   415     protected function _associate($version, $params)
       
   416     {
       
   417         $ret = array();
       
   418 
       
   419         if ($version >= 2.0) {
       
   420             $ret['ns'] = Zend_OpenId::NS_2_0;
       
   421         }
       
   422 
       
   423         if (isset($params['openid_assoc_type']) &&
       
   424             $params['openid_assoc_type'] == 'HMAC-SHA1') {
       
   425             $macFunc = 'sha1';
       
   426         } else if (isset($params['openid_assoc_type']) &&
       
   427             $params['openid_assoc_type'] == 'HMAC-SHA256' &&
       
   428             $version >= 2.0) {
       
   429             $macFunc = 'sha256';
       
   430         } else {
       
   431             $ret['error'] = 'Wrong "openid.assoc_type"';
       
   432             $ret['error-code'] = 'unsupported-type';
       
   433             return $ret;
       
   434         }
       
   435 
       
   436         $ret['assoc_type'] = $params['openid_assoc_type'];
       
   437 
       
   438         $secret = $this->_genSecret($macFunc);
       
   439 
       
   440         if (empty($params['openid_session_type']) ||
       
   441             $params['openid_session_type'] == 'no-encryption') {
       
   442             $ret['mac_key'] = base64_encode($secret);
       
   443         } else if (isset($params['openid_session_type']) &&
       
   444             $params['openid_session_type'] == 'DH-SHA1') {
       
   445             $dhFunc = 'sha1';
       
   446         } else if (isset($params['openid_session_type']) &&
       
   447             $params['openid_session_type'] == 'DH-SHA256' &&
       
   448             $version >= 2.0) {
       
   449             $dhFunc = 'sha256';
       
   450         } else {
       
   451             $ret['error'] = 'Wrong "openid.session_type"';
       
   452             $ret['error-code'] = 'unsupported-type';
       
   453             return $ret;
       
   454         }
       
   455 
       
   456         if (isset($params['openid_session_type'])) {
       
   457             $ret['session_type'] = $params['openid_session_type'];
       
   458         }
       
   459 
       
   460         if (isset($dhFunc)) {
       
   461             if (empty($params['openid_dh_consumer_public'])) {
       
   462                 $ret['error'] = 'Wrong "openid.dh_consumer_public"';
       
   463                 return $ret;
       
   464             }
       
   465             if (empty($params['openid_dh_gen'])) {
       
   466                 $g = pack('H*', Zend_OpenId::DH_G);
       
   467             } else {
       
   468                 $g = base64_decode($params['openid_dh_gen']);
       
   469             }
       
   470             if (empty($params['openid_dh_modulus'])) {
       
   471                 $p = pack('H*', Zend_OpenId::DH_P);
       
   472             } else {
       
   473                 $p = base64_decode($params['openid_dh_modulus']);
       
   474             }
       
   475 
       
   476             $dh = Zend_OpenId::createDhKey($p, $g);
       
   477             $dh_details = Zend_OpenId::getDhKeyDetails($dh);
       
   478 
       
   479             $sec = Zend_OpenId::computeDhSecret(
       
   480                 base64_decode($params['openid_dh_consumer_public']), $dh);
       
   481             if ($sec === false) {
       
   482                 $ret['error'] = 'Wrong "openid.session_type"';
       
   483                 $ret['error-code'] = 'unsupported-type';
       
   484                 return $ret;
       
   485             }
       
   486             $sec = Zend_OpenId::digest($dhFunc, $sec);
       
   487             $ret['dh_server_public'] = base64_encode(
       
   488                 Zend_OpenId::btwoc($dh_details['pub_key']));
       
   489             $ret['enc_mac_key']      = base64_encode($secret ^ $sec);
       
   490         }
       
   491 
       
   492         $handle = uniqid();
       
   493         $expiresIn = $this->_sessionTtl;
       
   494 
       
   495         $ret['assoc_handle'] = $handle;
       
   496         $ret['expires_in'] = $expiresIn;
       
   497 
       
   498         $this->_storage->addAssociation($handle,
       
   499             $macFunc, $secret, time() + $expiresIn);
       
   500 
       
   501         return $ret;
       
   502     }
       
   503 
       
   504     /**
       
   505      * Performs authentication (or authentication check).
       
   506      *
       
   507      * @param float $version OpenID version
       
   508      * @param array $params GET or POST request variables
       
   509      * @param bool $immediate enables or disables interaction with user
       
   510      * @param mixed $extensions extension object or array of extensions objects
       
   511      * @param Zend_Controller_Response_Abstract $response
       
   512      * @return array
       
   513      */
       
   514     protected function _checkId($version, $params, $immediate, $extensions=null,
       
   515         Zend_Controller_Response_Abstract $response = null)
       
   516     {
       
   517         $ret = array();
       
   518 
       
   519         if ($version >= 2.0) {
       
   520             $ret['openid.ns'] = Zend_OpenId::NS_2_0;
       
   521         }
       
   522         $root = $this->getSiteRoot($params);
       
   523         if ($root === false) {
       
   524             return false;
       
   525         }
       
   526 
       
   527         if (isset($params['openid_identity']) &&
       
   528             !$this->_storage->hasUser($params['openid_identity'])) {
       
   529             $ret['openid.mode'] = ($immediate && $version >= 2.0) ? 'setup_needed': 'cancel';
       
   530             return $ret;
       
   531         }
       
   532 
       
   533         /* Check if user already logged in into the server */
       
   534         if (!isset($params['openid_identity']) ||
       
   535             $this->_user->getLoggedInUser() !== $params['openid_identity']) {
       
   536             $params2 = array();
       
   537             foreach ($params as $key => $val) {
       
   538                 if (strpos($key, 'openid_ns_') === 0) {
       
   539                     $key = 'openid.ns.' . substr($key, strlen('openid_ns_'));
       
   540                 } else if (strpos($key, 'openid_sreg_') === 0) {
       
   541                     $key = 'openid.sreg.' . substr($key, strlen('openid_sreg_'));
       
   542                 } else if (strpos($key, 'openid_') === 0) {
       
   543                     $key = 'openid.' . substr($key, strlen('openid_'));
       
   544                 }
       
   545                 $params2[$key] = $val;
       
   546             }
       
   547             if ($immediate) {
       
   548                 $params2['openid.mode'] = 'checkid_setup';
       
   549                 $ret['openid.mode'] = ($version >= 2.0) ? 'setup_needed': 'id_res';
       
   550                 $ret['openid.user_setup_url'] = $this->_loginUrl
       
   551                     . (strpos($this->_loginUrl, '?') === false ? '?' : '&')
       
   552                     . Zend_OpenId::paramsToQuery($params2);
       
   553                 return $ret;
       
   554             } else {
       
   555                 /* Redirect to Server Login Screen */
       
   556                 Zend_OpenId::redirect($this->_loginUrl, $params2, $response);
       
   557                 return true;
       
   558             }
       
   559         }
       
   560 
       
   561         if (!Zend_OpenId_Extension::forAll($extensions, 'parseRequest', $params)) {
       
   562             $ret['openid.mode'] = ($immediate && $version >= 2.0) ? 'setup_needed': 'cancel';
       
   563             return $ret;
       
   564         }
       
   565 
       
   566         /* Check if user trusts to the consumer */
       
   567         $trusted = null;
       
   568         $sites = $this->_storage->getTrustedSites($params['openid_identity']);
       
   569         if (isset($params['openid_return_to'])) {
       
   570             $root = $params['openid_return_to'];
       
   571         }
       
   572         if (isset($sites[$root])) {
       
   573             $trusted = $sites[$root];
       
   574         } else {
       
   575             foreach ($sites as $site => $t) {
       
   576                 if (strpos($root, $site) === 0) {
       
   577                     $trusted = $t;
       
   578                     break;
       
   579                 } else {
       
   580                     /* OpenID 2.0 (9.2) check for realm wild-card matching */
       
   581                     $n = strpos($site, '://*.');
       
   582                     if ($n != false) {
       
   583                         $regex = '/^'
       
   584                                . preg_quote(substr($site, 0, $n+3), '/')
       
   585                                . '[A-Za-z1-9_\.]+?'
       
   586                                . preg_quote(substr($site, $n+4), '/')
       
   587                                . '/';
       
   588                         if (preg_match($regex, $root)) {
       
   589                             $trusted = $t;
       
   590                             break;
       
   591                         }
       
   592                     }
       
   593                 }
       
   594             }
       
   595         }
       
   596 
       
   597         if (is_array($trusted)) {
       
   598             if (!Zend_OpenId_Extension::forAll($extensions, 'checkTrustData', $trusted)) {
       
   599                 $trusted = null;
       
   600             }
       
   601         }
       
   602 
       
   603         if ($trusted === false) {
       
   604             $ret['openid.mode'] = 'cancel';
       
   605             return $ret;
       
   606         } else if ($trusted === null) {
       
   607             /* Redirect to Server Trust Screen */
       
   608             $params2 = array();
       
   609             foreach ($params as $key => $val) {
       
   610                 if (strpos($key, 'openid_ns_') === 0) {
       
   611                     $key = 'openid.ns.' . substr($key, strlen('openid_ns_'));
       
   612                 } else if (strpos($key, 'openid_sreg_') === 0) {
       
   613                     $key = 'openid.sreg.' . substr($key, strlen('openid_sreg_'));
       
   614                 } else if (strpos($key, 'openid_') === 0) {
       
   615                     $key = 'openid.' . substr($key, strlen('openid_'));
       
   616                 }
       
   617                 $params2[$key] = $val;
       
   618             }
       
   619             if ($immediate) {
       
   620                 $params2['openid.mode'] = 'checkid_setup';
       
   621                 $ret['openid.mode'] = ($version >= 2.0) ? 'setup_needed': 'id_res';
       
   622                 $ret['openid.user_setup_url'] = $this->_trustUrl
       
   623                     . (strpos($this->_trustUrl, '?') === false ? '?' : '&')
       
   624                     . Zend_OpenId::paramsToQuery($params2);
       
   625                 return $ret;
       
   626             } else {
       
   627                 Zend_OpenId::redirect($this->_trustUrl, $params2, $response);
       
   628                 return true;
       
   629             }
       
   630         }
       
   631 
       
   632         return $this->_respond($version, $ret, $params, $extensions);
       
   633     }
       
   634 
       
   635     /**
       
   636      * Perepares information to send back to consumer's authentication request,
       
   637      * signs it using shared secret and send back through HTTP redirection
       
   638      *
       
   639      * @param array $params GET or POST request variables
       
   640      * @param mixed $extensions extension object or array of extensions objects
       
   641      * @param Zend_Controller_Response_Abstract $response an optional response
       
   642      *  object to perform HTTP or HTML form redirection
       
   643      * @return bool
       
   644      */
       
   645     public function respondToConsumer($params, $extensions=null,
       
   646                            Zend_Controller_Response_Abstract $response = null)
       
   647     {
       
   648         $version = 1.1;
       
   649         if (isset($params['openid_ns']) &&
       
   650             $params['openid_ns'] == Zend_OpenId::NS_2_0) {
       
   651             $version = 2.0;
       
   652         }
       
   653         $ret = array();
       
   654         if ($version >= 2.0) {
       
   655             $ret['openid.ns'] = Zend_OpenId::NS_2_0;
       
   656         }
       
   657         $ret = $this->_respond($version, $ret, $params, $extensions);
       
   658         if (!empty($params['openid_return_to'])) {
       
   659             Zend_OpenId::redirect($params['openid_return_to'], $ret, $response);
       
   660         }
       
   661         return true;
       
   662     }
       
   663 
       
   664     /**
       
   665      * Perepares information to send back to consumer's authentication request
       
   666      * and signs it using shared secret.
       
   667      *
       
   668      * @param float $version OpenID protcol version
       
   669      * @param array $ret arguments to be send back to consumer
       
   670      * @param array $params GET or POST request variables
       
   671      * @param mixed $extensions extension object or array of extensions objects
       
   672      * @return array
       
   673      */
       
   674     protected function _respond($version, $ret, $params, $extensions=null)
       
   675     {
       
   676         if (empty($params['openid_assoc_handle']) ||
       
   677             !$this->_storage->getAssociation($params['openid_assoc_handle'],
       
   678                 $macFunc, $secret, $expires)) {
       
   679             /* Use dumb mode */
       
   680             if (!empty($params['openid_assoc_handle'])) {
       
   681                 $ret['openid.invalidate_handle'] = $params['openid_assoc_handle'];
       
   682             }
       
   683             $macFunc = $version >= 2.0 ? 'sha256' : 'sha1';
       
   684             $secret = $this->_genSecret($macFunc);
       
   685             $handle = uniqid();
       
   686             $expiresIn = $this->_sessionTtl;
       
   687             $this->_storage->addAssociation($handle,
       
   688                 $macFunc, $secret, time() + $expiresIn);
       
   689             $ret['openid.assoc_handle'] = $handle;
       
   690         } else {
       
   691             $ret['openid.assoc_handle'] = $params['openid_assoc_handle'];
       
   692         }
       
   693         if (isset($params['openid_return_to'])) {
       
   694             $ret['openid.return_to'] = $params['openid_return_to'];
       
   695         }
       
   696         if (isset($params['openid_claimed_id'])) {
       
   697             $ret['openid.claimed_id'] = $params['openid_claimed_id'];
       
   698         }
       
   699         if (isset($params['openid_identity'])) {
       
   700             $ret['openid.identity'] = $params['openid_identity'];
       
   701         }
       
   702 
       
   703         if ($version >= 2.0) {
       
   704             if (!empty($this->_opEndpoint)) {
       
   705                 $ret['openid.op_endpoint'] = $this->_opEndpoint;
       
   706             } else {
       
   707                 $ret['openid.op_endpoint'] = Zend_OpenId::selfUrl();
       
   708             }
       
   709         }
       
   710         $ret['openid.response_nonce'] = gmdate('Y-m-d\TH:i:s\Z') . uniqid();
       
   711         $ret['openid.mode'] = 'id_res';
       
   712 
       
   713         Zend_OpenId_Extension::forAll($extensions, 'prepareResponse', $ret);
       
   714 
       
   715         $signed = '';
       
   716         $data = '';
       
   717         foreach ($ret as $key => $val) {
       
   718             if (strpos($key, 'openid.') === 0) {
       
   719                 $key = substr($key, strlen('openid.'));
       
   720                 if (!empty($signed)) {
       
   721                     $signed .= ',';
       
   722                 }
       
   723                 $signed .= $key;
       
   724                 $data .= $key . ':' . $val . "\n";
       
   725             }
       
   726         }
       
   727         $signed .= ',signed';
       
   728         $data .= 'signed:' . $signed . "\n";
       
   729         $ret['openid.signed'] = $signed;
       
   730 
       
   731         $ret['openid.sig'] = base64_encode(
       
   732             Zend_OpenId::hashHmac($macFunc, $data, $secret));
       
   733 
       
   734         return $ret;
       
   735     }
       
   736 
       
   737     /**
       
   738      * Performs authentication validation for dumb consumers
       
   739      * Returns array of variables to push back to consumer.
       
   740      * It MUST contain 'is_valid' variable with value 'true' or 'false'.
       
   741      *
       
   742      * @param float $version OpenID version
       
   743      * @param array $params GET or POST request variables
       
   744      * @return array
       
   745      */
       
   746     protected function _checkAuthentication($version, $params)
       
   747     {
       
   748         $ret = array();
       
   749         if ($version >= 2.0) {
       
   750             $ret['ns'] = Zend_OpenId::NS_2_0;
       
   751         }
       
   752         $ret['openid.mode'] = 'id_res';
       
   753 
       
   754         if (empty($params['openid_assoc_handle']) ||
       
   755             empty($params['openid_signed']) ||
       
   756             empty($params['openid_sig']) ||
       
   757             !$this->_storage->getAssociation($params['openid_assoc_handle'],
       
   758                 $macFunc, $secret, $expires)) {
       
   759             $ret['is_valid'] = 'false';
       
   760             return $ret;
       
   761         }
       
   762 
       
   763         $signed = explode(',', $params['openid_signed']);
       
   764         $data = '';
       
   765         foreach ($signed as $key) {
       
   766             $data .= $key . ':';
       
   767             if ($key == 'mode') {
       
   768                 $data .= "id_res\n";
       
   769             } else {
       
   770                 $data .= $params['openid_' . strtr($key,'.','_')]."\n";
       
   771             }
       
   772         }
       
   773         if ($this->_secureStringCompare(base64_decode($params['openid_sig']),
       
   774             Zend_OpenId::hashHmac($macFunc, $data, $secret))) {
       
   775             $ret['is_valid'] = 'true';
       
   776         } else {
       
   777             $ret['is_valid'] = 'false';
       
   778         }
       
   779         return $ret;
       
   780     }
       
   781     
       
   782     /**
       
   783      * Securely compare two strings for equality while avoided C level memcmp()
       
   784      * optimisations capable of leaking timing information useful to an attacker
       
   785      * attempting to iteratively guess the unknown string (e.g. password) being
       
   786      * compared against.
       
   787      *
       
   788      * @param string $a
       
   789      * @param string $b
       
   790      * @return bool
       
   791      */
       
   792     protected function _secureStringCompare($a, $b)
       
   793     {
       
   794         if (strlen($a) !== strlen($b)) {
       
   795             return false;
       
   796         }
       
   797         $result = 0;
       
   798         for ($i = 0; $i < strlen($a); $i++) {
       
   799             $result |= ord($a[$i]) ^ ord($b[$i]);
       
   800         }
       
   801         return $result == 0;
       
   802     }
       
   803 }