File indexing completed on 2024-05-12 06:02:49

0001 <?php
0002 
0003 /**
0004  * Zend Framework
0005  *
0006  * LICENSE
0007  *
0008  * This source file is subject to the new BSD license that is bundled
0009  * with this package in the file LICENSE.txt.
0010  * It is also available through the world-wide-web at this URL:
0011  * http://framework.zend.com/license/new-bsd
0012  * If you did not receive a copy of the license and are unable to
0013  * obtain it through the world-wide-web, please send an email
0014  * to license@zend.com so we can send you a copy immediately.
0015  *
0016  * @category   Zend
0017  * @package    Zend_OpenId
0018  * @subpackage Zend_OpenId_Provider
0019  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0020  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0021  * @version    $Id$
0022  */
0023 
0024 /**
0025  * @see Zend_OpenId
0026  */
0027 // require_once "Zend/OpenId.php";
0028 
0029 /**
0030  * @see Zend_OpenId_Extension
0031  */
0032 // require_once "Zend/OpenId/Extension.php";
0033 
0034 /**
0035  * OpenID provider (server) implementation
0036  *
0037  * @category   Zend
0038  * @package    Zend_OpenId
0039  * @subpackage Zend_OpenId_Provider
0040  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0041  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0042  */
0043 class Zend_OpenId_Provider
0044 {
0045 
0046     /**
0047      * Reference to an implementation of storage object
0048      *
0049      * @var Zend_OpenId_Provider_Storage $_storage
0050      */
0051     private $_storage;
0052 
0053     /**
0054      * Reference to an implementation of user object
0055      *
0056      * @var Zend_OpenId_Provider_User $_user
0057      */
0058     private $_user;
0059 
0060     /**
0061      * Time to live of association session in secconds
0062      *
0063      * @var integer $_sessionTtl
0064      */
0065     private $_sessionTtl;
0066 
0067     /**
0068      * URL to peform interactive user login
0069      *
0070      * @var string $_loginUrl
0071      */
0072     private $_loginUrl;
0073 
0074     /**
0075      * URL to peform interactive validation of consumer by user
0076      *
0077      * @var string $_trustUrl
0078      */
0079     private $_trustUrl;
0080 
0081     /**
0082      * The OP Endpoint URL
0083      *
0084      * @var string $_opEndpoint
0085      */
0086     private $_opEndpoint;
0087 
0088     /**
0089      * Constructs a Zend_OpenId_Provider object with given parameters.
0090      *
0091      * @param string $loginUrl is an URL that provides login screen for
0092      *  end-user (by default it is the same URL with additional GET variable
0093      *  openid.action=login)
0094      * @param string $trustUrl is an URL that shows a question if end-user
0095      *  trust to given consumer (by default it is the same URL with additional
0096      *  GET variable openid.action=trust)
0097      * @param Zend_OpenId_Provider_User $user is an object for communication
0098      *  with User-Agent and store information about logged-in user (it is a
0099      *  Zend_OpenId_Provider_User_Session object by default)
0100      * @param Zend_OpenId_Provider_Storage $storage is an object for keeping
0101      *  persistent database (it is a Zend_OpenId_Provider_Storage_File object
0102      *  by default)
0103      * @param integer $sessionTtl is a default time to live for association
0104      *   session in seconds (1 hour by default). Consumer must reestablish
0105      *   association after that time.
0106      */
0107     public function __construct($loginUrl = null,
0108                                 $trustUrl = null,
0109                                 Zend_OpenId_Provider_User $user = null,
0110                                 Zend_OpenId_Provider_Storage $storage = null,
0111                                 $sessionTtl = 3600)
0112     {
0113         if ($loginUrl === null) {
0114             $loginUrl = Zend_OpenId::selfUrl() . '?openid.action=login';
0115         } else {
0116             $loginUrl = Zend_OpenId::absoluteUrl($loginUrl);
0117         }
0118         $this->_loginUrl = $loginUrl;
0119         if ($trustUrl === null) {
0120             $trustUrl = Zend_OpenId::selfUrl() . '?openid.action=trust';
0121         } else {
0122             $trustUrl = Zend_OpenId::absoluteUrl($trustUrl);
0123         }
0124         $this->_trustUrl = $trustUrl;
0125         if ($user === null) {
0126             // require_once "Zend/OpenId/Provider/User/Session.php";
0127             $this->_user = new Zend_OpenId_Provider_User_Session();
0128         } else {
0129             $this->_user = $user;
0130         }
0131         if ($storage === null) {
0132             // require_once "Zend/OpenId/Provider/Storage/File.php";
0133             $this->_storage = new Zend_OpenId_Provider_Storage_File();
0134         } else {
0135             $this->_storage = $storage;
0136         }
0137         $this->_sessionTtl = $sessionTtl;
0138     }
0139 
0140     /**
0141      * Sets the OP Endpoint URL
0142      *
0143      * @param string $url the OP Endpoint URL
0144      * @return null
0145      */
0146     public function setOpEndpoint($url)
0147     {
0148         $this->_opEndpoint = $url;
0149     }
0150 
0151     /**
0152      * Registers a new user with given $id and $password
0153      * Returns true in case of success and false if user with given $id already
0154      * exists
0155      *
0156      * @param string $id user identity URL
0157      * @param string $password encoded user password
0158      * @return bool
0159      */
0160     public function register($id, $password)
0161     {
0162         if (!Zend_OpenId::normalize($id) || empty($id)) {
0163             return false;
0164         }
0165         return $this->_storage->addUser($id, md5($id.$password));
0166     }
0167 
0168     /**
0169      * Returns true if user with given $id exists and false otherwise
0170      *
0171      * @param string $id user identity URL
0172      * @return bool
0173      */
0174     public function hasUser($id) {
0175         if (!Zend_OpenId::normalize($id)) {
0176             return false;
0177         }
0178         return $this->_storage->hasUser($id);
0179     }
0180 
0181     /**
0182      * Performs login of user with given $id and $password
0183      * Returns true in case of success and false otherwise
0184      *
0185      * @param string $id user identity URL
0186      * @param string $password user password
0187      * @return bool
0188      */
0189     public function login($id, $password)
0190     {
0191         if (!Zend_OpenId::normalize($id)) {
0192             return false;
0193         }
0194         if (!$this->_storage->checkUser($id, md5($id.$password))) {
0195             return false;
0196         }
0197         $this->_user->setLoggedInUser($id);
0198         return true;
0199     }
0200 
0201     /**
0202      * Performs logout. Clears information about logged in user.
0203      *
0204      * @return void
0205      */
0206     public function logout()
0207     {
0208         $this->_user->delLoggedInUser();
0209         return true;
0210     }
0211 
0212     /**
0213      * Returns identity URL of current logged in user or false
0214      *
0215      * @return mixed
0216      */
0217     public function getLoggedInUser() {
0218         return $this->_user->getLoggedInUser();
0219     }
0220 
0221     /**
0222      * Retrieve consumer's root URL from request query.
0223      * Returns URL or false in case of failure
0224      *
0225      * @param array $params query arguments
0226      * @return mixed
0227      */
0228     public function getSiteRoot($params)
0229     {
0230         $version = 1.1;
0231         if (isset($params['openid_ns']) &&
0232             $params['openid_ns'] == Zend_OpenId::NS_2_0) {
0233             $version = 2.0;
0234         }
0235         if ($version >= 2.0 && isset($params['openid_realm'])) {
0236             $root = $params['openid_realm'];
0237         } else if ($version < 2.0 && isset($params['openid_trust_root'])) {
0238             $root = $params['openid_trust_root'];
0239         } else if (isset($params['openid_return_to'])) {
0240             $root = $params['openid_return_to'];
0241         } else {
0242             return false;
0243         }
0244         if (Zend_OpenId::normalizeUrl($root) && !empty($root)) {
0245             return $root;
0246         }
0247         return false;
0248     }
0249 
0250     /**
0251      * Allows consumer with given root URL to authenticate current logged
0252      * in user. Returns true on success and false on error.
0253      *
0254      * @param string $root root URL
0255      * @param mixed $extensions extension object or array of extensions objects
0256      * @return bool
0257      */
0258     public function allowSite($root, $extensions=null)
0259     {
0260         $id = $this->getLoggedInUser();
0261         if ($id === false) {
0262             return false;
0263         }
0264         if ($extensions !== null) {
0265             $data = array();
0266             Zend_OpenId_Extension::forAll($extensions, 'getTrustData', $data);
0267         } else {
0268             $data = true;
0269         }
0270         $this->_storage->addSite($id, $root, $data);
0271         return true;
0272     }
0273 
0274     /**
0275      * Prohibit consumer with given root URL to authenticate current logged
0276      * in user. Returns true on success and false on error.
0277      *
0278      * @param string $root root URL
0279      * @return bool
0280      */
0281     public function denySite($root)
0282     {
0283         $id = $this->getLoggedInUser();
0284         if ($id === false) {
0285             return false;
0286         }
0287         $this->_storage->addSite($id, $root, false);
0288         return true;
0289     }
0290 
0291     /**
0292      * Delete consumer with given root URL from known sites of current logged
0293      * in user. Next time this consumer will try to authenticate the user,
0294      * Provider will ask user's confirmation.
0295      * Returns true on success and false on error.
0296      *
0297      * @param string $root root URL
0298      * @return bool
0299      */
0300     public function delSite($root)
0301     {
0302         $id = $this->getLoggedInUser();
0303         if ($id === false) {
0304             return false;
0305         }
0306         $this->_storage->addSite($id, $root, null);
0307         return true;
0308     }
0309 
0310     /**
0311      * Returns list of known consumers for current logged in user or false
0312      * if he is not logged in.
0313      *
0314      * @return mixed
0315      */
0316     public function getTrustedSites()
0317     {
0318         $id = $this->getLoggedInUser();
0319         if ($id === false) {
0320             return false;
0321         }
0322         return $this->_storage->getTrustedSites($id);
0323     }
0324 
0325     /**
0326      * Handles HTTP request from consumer
0327      *
0328      * @param array $params GET or POST variables. If this parameter is omited
0329      *  or set to null, then $_GET or $_POST superglobal variable is used
0330      *  according to REQUEST_METHOD.
0331      * @param mixed $extensions extension object or array of extensions objects
0332      * @param Zend_Controller_Response_Abstract $response an optional response
0333      *  object to perform HTTP or HTML form redirection
0334      * @return mixed
0335      */
0336     public function handle($params=null, $extensions=null,
0337                            Zend_Controller_Response_Abstract $response = null)
0338     {
0339         if ($params === null) {
0340             if ($_SERVER["REQUEST_METHOD"] == "GET") {
0341                 $params = $_GET;
0342             } else if ($_SERVER["REQUEST_METHOD"] == "POST") {
0343                 $params = $_POST;
0344             } else {
0345                 return false;
0346             }
0347         }
0348         $version = 1.1;
0349         if (isset($params['openid_ns']) &&
0350             $params['openid_ns'] == Zend_OpenId::NS_2_0) {
0351             $version = 2.0;
0352         }
0353         if (isset($params['openid_mode'])) {
0354             if ($params['openid_mode'] == 'associate') {
0355                 $response = $this->_associate($version, $params);
0356                 $ret = '';
0357                 foreach ($response as $key => $val) {
0358                     $ret .= $key . ':' . $val . "\n";
0359                 }
0360                 return $ret;
0361             } else if ($params['openid_mode'] == 'checkid_immediate') {
0362                 $ret = $this->_checkId($version, $params, 1, $extensions, $response);
0363                 if (is_bool($ret)) return $ret;
0364                 if (!empty($params['openid_return_to'])) {
0365                     Zend_OpenId::redirect($params['openid_return_to'], $ret, $response);
0366                 }
0367                 return true;
0368             } else if ($params['openid_mode'] == 'checkid_setup') {
0369                 $ret = $this->_checkId($version, $params, 0, $extensions, $response);
0370                 if (is_bool($ret)) return $ret;
0371                 if (!empty($params['openid_return_to'])) {
0372                     Zend_OpenId::redirect($params['openid_return_to'], $ret, $response);
0373                 }
0374                 return true;
0375             } else if ($params['openid_mode'] == 'check_authentication') {
0376                 $response = $this->_checkAuthentication($version, $params);
0377                 $ret = '';
0378                 foreach ($response as $key => $val) {
0379                     $ret .= $key . ':' . $val . "\n";
0380                 }
0381                 return $ret;
0382             }
0383         }
0384         return false;
0385     }
0386 
0387     /**
0388      * Generates a secret key for given hash function, returns RAW key or false
0389      * if function is not supported
0390      *
0391      * @param string $func hash function (sha1 or sha256)
0392      * @return mixed
0393      */
0394     protected function _genSecret($func)
0395     {
0396         if ($func == 'sha1') {
0397             $macLen = 20; /* 160 bit */
0398         } else if ($func == 'sha256') {
0399             $macLen = 32; /* 256 bit */
0400         } else {
0401             return false;
0402         }
0403         return Zend_OpenId::randomBytes($macLen);
0404     }
0405 
0406     /**
0407      * Processes association request from OpenID consumerm generates secret
0408      * shared key and send it back using Diffie-Hellman encruption.
0409      * Returns array of variables to push back to consumer.
0410      *
0411      * @param float $version OpenID version
0412      * @param array $params GET or POST request variables
0413      * @return array
0414      */
0415     protected function _associate($version, $params)
0416     {
0417         $ret = array();
0418 
0419         if ($version >= 2.0) {
0420             $ret['ns'] = Zend_OpenId::NS_2_0;
0421         }
0422 
0423         if (isset($params['openid_assoc_type']) &&
0424             $params['openid_assoc_type'] == 'HMAC-SHA1') {
0425             $macFunc = 'sha1';
0426         } else if (isset($params['openid_assoc_type']) &&
0427             $params['openid_assoc_type'] == 'HMAC-SHA256' &&
0428             $version >= 2.0) {
0429             $macFunc = 'sha256';
0430         } else {
0431             $ret['error'] = 'Wrong "openid.assoc_type"';
0432             $ret['error-code'] = 'unsupported-type';
0433             return $ret;
0434         }
0435 
0436         $ret['assoc_type'] = $params['openid_assoc_type'];
0437 
0438         $secret = $this->_genSecret($macFunc);
0439 
0440         if (empty($params['openid_session_type']) ||
0441             $params['openid_session_type'] == 'no-encryption') {
0442             $ret['mac_key'] = base64_encode($secret);
0443         } else if (isset($params['openid_session_type']) &&
0444             $params['openid_session_type'] == 'DH-SHA1') {
0445             $dhFunc = 'sha1';
0446         } else if (isset($params['openid_session_type']) &&
0447             $params['openid_session_type'] == 'DH-SHA256' &&
0448             $version >= 2.0) {
0449             $dhFunc = 'sha256';
0450         } else {
0451             $ret['error'] = 'Wrong "openid.session_type"';
0452             $ret['error-code'] = 'unsupported-type';
0453             return $ret;
0454         }
0455 
0456         if (isset($params['openid_session_type'])) {
0457             $ret['session_type'] = $params['openid_session_type'];
0458         }
0459 
0460         if (isset($dhFunc)) {
0461             if (empty($params['openid_dh_consumer_public'])) {
0462                 $ret['error'] = 'Wrong "openid.dh_consumer_public"';
0463                 return $ret;
0464             }
0465             if (empty($params['openid_dh_gen'])) {
0466                 $g = pack('H*', Zend_OpenId::DH_G);
0467             } else {
0468                 $g = base64_decode($params['openid_dh_gen']);
0469             }
0470             if (empty($params['openid_dh_modulus'])) {
0471                 $p = pack('H*', Zend_OpenId::DH_P);
0472             } else {
0473                 $p = base64_decode($params['openid_dh_modulus']);
0474             }
0475 
0476             $dh = Zend_OpenId::createDhKey($p, $g);
0477             $dh_details = Zend_OpenId::getDhKeyDetails($dh);
0478 
0479             $sec = Zend_OpenId::computeDhSecret(
0480                 base64_decode($params['openid_dh_consumer_public']), $dh);
0481             if ($sec === false) {
0482                 $ret['error'] = 'Wrong "openid.session_type"';
0483                 $ret['error-code'] = 'unsupported-type';
0484                 return $ret;
0485             }
0486             $sec = Zend_OpenId::digest($dhFunc, $sec);
0487             $ret['dh_server_public'] = base64_encode(
0488                 Zend_OpenId::btwoc($dh_details['pub_key']));
0489             $ret['enc_mac_key']      = base64_encode($secret ^ $sec);
0490         }
0491 
0492         $handle = uniqid();
0493         $expiresIn = $this->_sessionTtl;
0494 
0495         $ret['assoc_handle'] = $handle;
0496         $ret['expires_in'] = $expiresIn;
0497 
0498         $this->_storage->addAssociation($handle,
0499             $macFunc, $secret, time() + $expiresIn);
0500 
0501         return $ret;
0502     }
0503 
0504     /**
0505      * Performs authentication (or authentication check).
0506      *
0507      * @param float $version OpenID version
0508      * @param array $params GET or POST request variables
0509      * @param bool $immediate enables or disables interaction with user
0510      * @param mixed $extensions extension object or array of extensions objects
0511      * @param Zend_Controller_Response_Abstract $response
0512      * @return array
0513      */
0514     protected function _checkId($version, $params, $immediate, $extensions=null,
0515         Zend_Controller_Response_Abstract $response = null)
0516     {
0517         $ret = array();
0518 
0519         if ($version >= 2.0) {
0520             $ret['openid.ns'] = Zend_OpenId::NS_2_0;
0521         }
0522         $root = $this->getSiteRoot($params);
0523         if ($root === false) {
0524             return false;
0525         }
0526 
0527         if (isset($params['openid_identity']) &&
0528             !$this->_storage->hasUser($params['openid_identity'])) {
0529             $ret['openid.mode'] = ($immediate && $version >= 2.0) ? 'setup_needed': 'cancel';
0530             return $ret;
0531         }
0532 
0533         /* Check if user already logged in into the server */
0534         if (!isset($params['openid_identity']) ||
0535             $this->_user->getLoggedInUser() !== $params['openid_identity']) {
0536             $params2 = array();
0537             foreach ($params as $key => $val) {
0538                 if (strpos($key, 'openid_ns_') === 0) {
0539                     $key = 'openid.ns.' . substr($key, strlen('openid_ns_'));
0540                 } else if (strpos($key, 'openid_sreg_') === 0) {
0541                     $key = 'openid.sreg.' . substr($key, strlen('openid_sreg_'));
0542                 } else if (strpos($key, 'openid_') === 0) {
0543                     $key = 'openid.' . substr($key, strlen('openid_'));
0544                 }
0545                 $params2[$key] = $val;
0546             }
0547             if ($immediate) {
0548                 $params2['openid.mode'] = 'checkid_setup';
0549                 $ret['openid.mode'] = ($version >= 2.0) ? 'setup_needed': 'id_res';
0550                 $ret['openid.user_setup_url'] = $this->_loginUrl
0551                     . (strpos($this->_loginUrl, '?') === false ? '?' : '&')
0552                     . Zend_OpenId::paramsToQuery($params2);
0553                 return $ret;
0554             } else {
0555                 /* Redirect to Server Login Screen */
0556                 Zend_OpenId::redirect($this->_loginUrl, $params2, $response);
0557                 return true;
0558             }
0559         }
0560 
0561         if (!Zend_OpenId_Extension::forAll($extensions, 'parseRequest', $params)) {
0562             $ret['openid.mode'] = ($immediate && $version >= 2.0) ? 'setup_needed': 'cancel';
0563             return $ret;
0564         }
0565 
0566         /* Check if user trusts to the consumer */
0567         $trusted = null;
0568         $sites = $this->_storage->getTrustedSites($params['openid_identity']);
0569         if (isset($params['openid_return_to'])) {
0570             $root = $params['openid_return_to'];
0571         }
0572         if (isset($sites[$root])) {
0573             $trusted = $sites[$root];
0574         } else {
0575             foreach ($sites as $site => $t) {
0576                 if (strpos($root, $site) === 0) {
0577                     $trusted = $t;
0578                     break;
0579                 } else {
0580                     /* OpenID 2.0 (9.2) check for realm wild-card matching */
0581                     $n = strpos($site, '://*.');
0582                     if ($n != false) {
0583                         $regex = '/^'
0584                                . preg_quote(substr($site, 0, $n+3), '/')
0585                                . '[A-Za-z1-9_\.]+?'
0586                                . preg_quote(substr($site, $n+4), '/')
0587                                . '/';
0588                         if (preg_match($regex, $root)) {
0589                             $trusted = $t;
0590                             break;
0591                         }
0592                     }
0593                 }
0594             }
0595         }
0596 
0597         if (is_array($trusted)) {
0598             if (!Zend_OpenId_Extension::forAll($extensions, 'checkTrustData', $trusted)) {
0599                 $trusted = null;
0600             }
0601         }
0602 
0603         if ($trusted === false) {
0604             $ret['openid.mode'] = 'cancel';
0605             return $ret;
0606         } else if ($trusted === null) {
0607             /* Redirect to Server Trust Screen */
0608             $params2 = array();
0609             foreach ($params as $key => $val) {
0610                 if (strpos($key, 'openid_ns_') === 0) {
0611                     $key = 'openid.ns.' . substr($key, strlen('openid_ns_'));
0612                 } else if (strpos($key, 'openid_sreg_') === 0) {
0613                     $key = 'openid.sreg.' . substr($key, strlen('openid_sreg_'));
0614                 } else if (strpos($key, 'openid_') === 0) {
0615                     $key = 'openid.' . substr($key, strlen('openid_'));
0616                 }
0617                 $params2[$key] = $val;
0618             }
0619             if ($immediate) {
0620                 $params2['openid.mode'] = 'checkid_setup';
0621                 $ret['openid.mode'] = ($version >= 2.0) ? 'setup_needed': 'id_res';
0622                 $ret['openid.user_setup_url'] = $this->_trustUrl
0623                     . (strpos($this->_trustUrl, '?') === false ? '?' : '&')
0624                     . Zend_OpenId::paramsToQuery($params2);
0625                 return $ret;
0626             } else {
0627                 Zend_OpenId::redirect($this->_trustUrl, $params2, $response);
0628                 return true;
0629             }
0630         }
0631 
0632         return $this->_respond($version, $ret, $params, $extensions);
0633     }
0634 
0635     /**
0636      * Perepares information to send back to consumer's authentication request,
0637      * signs it using shared secret and send back through HTTP redirection
0638      *
0639      * @param array $params GET or POST request variables
0640      * @param mixed $extensions extension object or array of extensions objects
0641      * @param Zend_Controller_Response_Abstract $response an optional response
0642      *  object to perform HTTP or HTML form redirection
0643      * @return bool
0644      */
0645     public function respondToConsumer($params, $extensions=null,
0646                            Zend_Controller_Response_Abstract $response = null)
0647     {
0648         $version = 1.1;
0649         if (isset($params['openid_ns']) &&
0650             $params['openid_ns'] == Zend_OpenId::NS_2_0) {
0651             $version = 2.0;
0652         }
0653         $ret = array();
0654         if ($version >= 2.0) {
0655             $ret['openid.ns'] = Zend_OpenId::NS_2_0;
0656         }
0657         $ret = $this->_respond($version, $ret, $params, $extensions);
0658         if (!empty($params['openid_return_to'])) {
0659             Zend_OpenId::redirect($params['openid_return_to'], $ret, $response);
0660         }
0661         return true;
0662     }
0663 
0664     /**
0665      * Perepares information to send back to consumer's authentication request
0666      * and signs it using shared secret.
0667      *
0668      * @param float $version OpenID protcol version
0669      * @param array $ret arguments to be send back to consumer
0670      * @param array $params GET or POST request variables
0671      * @param mixed $extensions extension object or array of extensions objects
0672      * @return array
0673      */
0674     protected function _respond($version, $ret, $params, $extensions=null)
0675     {
0676         if (empty($params['openid_assoc_handle']) ||
0677             !$this->_storage->getAssociation($params['openid_assoc_handle'],
0678                 $macFunc, $secret, $expires)) {
0679             /* Use dumb mode */
0680             if (!empty($params['openid_assoc_handle'])) {
0681                 $ret['openid.invalidate_handle'] = $params['openid_assoc_handle'];
0682             }
0683             $macFunc = $version >= 2.0 ? 'sha256' : 'sha1';
0684             $secret = $this->_genSecret($macFunc);
0685             $handle = uniqid();
0686             $expiresIn = $this->_sessionTtl;
0687             $this->_storage->addAssociation($handle,
0688                 $macFunc, $secret, time() + $expiresIn);
0689             $ret['openid.assoc_handle'] = $handle;
0690         } else {
0691             $ret['openid.assoc_handle'] = $params['openid_assoc_handle'];
0692         }
0693         if (isset($params['openid_return_to'])) {
0694             $ret['openid.return_to'] = $params['openid_return_to'];
0695         }
0696         if (isset($params['openid_claimed_id'])) {
0697             $ret['openid.claimed_id'] = $params['openid_claimed_id'];
0698         }
0699         if (isset($params['openid_identity'])) {
0700             $ret['openid.identity'] = $params['openid_identity'];
0701         }
0702 
0703         if ($version >= 2.0) {
0704             if (!empty($this->_opEndpoint)) {
0705                 $ret['openid.op_endpoint'] = $this->_opEndpoint;
0706             } else {
0707                 $ret['openid.op_endpoint'] = Zend_OpenId::selfUrl();
0708             }
0709         }
0710         $ret['openid.response_nonce'] = gmdate('Y-m-d\TH:i:s\Z') . uniqid();
0711         $ret['openid.mode'] = 'id_res';
0712 
0713         Zend_OpenId_Extension::forAll($extensions, 'prepareResponse', $ret);
0714 
0715         $signed = '';
0716         $data = '';
0717         foreach ($ret as $key => $val) {
0718             if (strpos($key, 'openid.') === 0) {
0719                 $key = substr($key, strlen('openid.'));
0720                 if (!empty($signed)) {
0721                     $signed .= ',';
0722                 }
0723                 $signed .= $key;
0724                 $data .= $key . ':' . $val . "\n";
0725             }
0726         }
0727         $signed .= ',signed';
0728         $data .= 'signed:' . $signed . "\n";
0729         $ret['openid.signed'] = $signed;
0730 
0731         $ret['openid.sig'] = base64_encode(
0732             Zend_OpenId::hashHmac($macFunc, $data, $secret));
0733 
0734         return $ret;
0735     }
0736 
0737     /**
0738      * Performs authentication validation for dumb consumers
0739      * Returns array of variables to push back to consumer.
0740      * It MUST contain 'is_valid' variable with value 'true' or 'false'.
0741      *
0742      * @param float $version OpenID version
0743      * @param array $params GET or POST request variables
0744      * @return array
0745      */
0746     protected function _checkAuthentication($version, $params)
0747     {
0748         $ret = array();
0749         if ($version >= 2.0) {
0750             $ret['ns'] = Zend_OpenId::NS_2_0;
0751         }
0752         $ret['openid.mode'] = 'id_res';
0753 
0754         if (empty($params['openid_assoc_handle']) ||
0755             empty($params['openid_signed']) ||
0756             empty($params['openid_sig']) ||
0757             !$this->_storage->getAssociation($params['openid_assoc_handle'],
0758                 $macFunc, $secret, $expires)) {
0759             $ret['is_valid'] = 'false';
0760             return $ret;
0761         }
0762 
0763         $signed = explode(',', $params['openid_signed']);
0764         $data = '';
0765         foreach ($signed as $key) {
0766             $data .= $key . ':';
0767             if ($key == 'mode') {
0768                 $data .= "id_res\n";
0769             } else {
0770                 $data .= $params['openid_' . strtr($key,'.','_')]."\n";
0771             }
0772         }
0773         if ($this->_secureStringCompare(base64_decode($params['openid_sig']),
0774             Zend_OpenId::hashHmac($macFunc, $data, $secret))) {
0775             $ret['is_valid'] = 'true';
0776         } else {
0777             $ret['is_valid'] = 'false';
0778         }
0779         return $ret;
0780     }
0781 
0782     /**
0783      * Securely compare two strings for equality while avoided C level memcmp()
0784      * optimisations capable of leaking timing information useful to an attacker
0785      * attempting to iteratively guess the unknown string (e.g. password) being
0786      * compared against.
0787      *
0788      * @param string $a
0789      * @param string $b
0790      * @return bool
0791      */
0792     protected function _secureStringCompare($a, $b)
0793     {
0794         if (strlen($a) !== strlen($b)) {
0795             return false;
0796         }
0797         $result = 0;
0798         for ($i = 0; $i < strlen($a); $i++) {
0799             $result |= ord($a[$i]) ^ ord($b[$i]);
0800         }
0801         return $result == 0;
0802     }
0803 }