PageRenderTime 426ms CodeModel.GetById 82ms app.highlight 281ms RepoModel.GetById 54ms app.codeStats 0ms

/src/application/libraries/Zend/OpenId/Consumer.php

https://bitbucket.org/masnug/grc276-blog-laravel
PHP | 958 lines | 649 code | 74 blank | 235 comment | 185 complexity | 8bb4aa9bef51d6ffdcb0a1e5a0b0c06a MD5 | raw file
  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-2011 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 23775 2011-03-01 17:25:24Z ralph $
 22 */
 23
 24/**
 25 * @see Zend_OpenId
 26 */
 27require_once "Zend/OpenId.php";
 28
 29/**
 30 * @see Zend_OpenId_Extension
 31 */
 32require_once "Zend/OpenId/Extension.php";
 33
 34/**
 35 * @see Zend_OpenId_Consumer_Storage
 36 */
 37require_once "Zend/OpenId/Consumer/Storage.php";
 38
 39/**
 40 * @see Zend_Http_Client
 41 */
 42require_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-2011 Zend Technologies USA Inc. (http://www.zend.com)
 51 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 52 */
 53class 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}