web/Zend/OpenId/Consumer.php
changeset 0 4eba9c11703f
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/Zend/OpenId/Consumer.php	Mon Dec 13 18:29:26 2010 +0100
@@ -0,0 +1,958 @@
+<?php
+
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_OpenId
+ * @subpackage Zend_OpenId_Consumer
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id: Consumer.php 20096 2010-01-06 02:05:09Z bkarwin $
+ */
+
+/**
+ * @see Zend_OpenId
+ */
+require_once "Zend/OpenId.php";
+
+/**
+ * @see Zend_OpenId_Extension
+ */
+require_once "Zend/OpenId/Extension.php";
+
+/**
+ * @see Zend_OpenId_Consumer_Storage
+ */
+require_once "Zend/OpenId/Consumer/Storage.php";
+
+/**
+ * @see Zend_Http_Client
+ */
+require_once 'Zend/Http/Client.php';
+
+/**
+ * OpenID consumer implementation
+ *
+ * @category   Zend
+ * @package    Zend_OpenId
+ * @subpackage Zend_OpenId_Consumer
+ * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_OpenId_Consumer
+{
+
+    /**
+     * Reference to an implementation of storage object
+     *
+     * @var Zend_OpenId_Consumer_Storage $_storage
+     */
+    protected $_storage = null;
+
+    /**
+     * Enables or disables consumer to use association with server based on
+     * Diffie-Hellman key agreement
+     *
+     * @var Zend_OpenId_Consumer_Storage $_dumbMode
+     */
+    protected $_dumbMode = false;
+
+    /**
+     * Internal cache to prevent unnecessary access to storage
+     *
+     * @var array $_cache
+     */
+    protected $_cache = array();
+
+    /**
+     * HTTP client to make HTTP requests
+     *
+     * @var Zend_Http_Client $_httpClient
+     */
+    private $_httpClient = null;
+
+    /**
+     * HTTP session to store climed_id between requests
+     *
+     * @var Zend_Session_Namespace $_session
+     */
+    private $_session = null;
+
+    /**
+     * Last error message for logi, check or verify failure
+     *
+     * @var string $_error
+     */
+    private $_error = '';
+
+    /**
+     * Constructs a Zend_OpenId_Consumer object with given $storage.
+     * Enables or disables future association with server based on
+     * Diffie-Hellman key agreement.
+     *
+     * @param Zend_OpenId_Consumer_Storage $storage implementation of custom
+     *  storage object
+     * @param bool $dumbMode Enables or disables consumer to use association
+     *  with server based on Diffie-Hellman key agreement
+     */
+    public function __construct(Zend_OpenId_Consumer_Storage $storage = null,
+                                $dumbMode = false)
+    {
+        if ($storage === null) {
+            require_once "Zend/OpenId/Consumer/Storage/File.php";
+            $this->_storage = new Zend_OpenId_Consumer_Storage_File();
+        } else {
+            $this->_storage = $storage;
+        }
+        $this->_dumbMode = $dumbMode;
+    }
+
+    /**
+     * Performs check (with possible user interaction) of OpenID identity.
+     *
+     * This is the first step of OpenID authentication process.
+     * On success the function does not return (it does HTTP redirection to
+     * server and exits). On failure it returns false.
+     *
+     * @param string $id OpenID identity
+     * @param string $returnTo URL to redirect response from server to
+     * @param string $root HTTP URL to identify consumer on server
+     * @param mixed $extensions extension object or array of extensions objects
+     * @param Zend_Controller_Response_Abstract $response an optional response
+     *  object to perform HTTP or HTML form redirection
+     * @return bool
+     */
+    public function login($id, $returnTo = null, $root = null, $extensions = null,
+                          Zend_Controller_Response_Abstract $response = null)
+    {
+        return $this->_checkId(
+            false,
+            $id,
+            $returnTo,
+            $root,
+            $extensions,
+            $response);
+    }
+
+    /**
+     * Performs immediate check (without user interaction) of OpenID identity.
+     *
+     * This is the first step of OpenID authentication process.
+     * On success the function does not return (it does HTTP redirection to
+     * server and exits). On failure it returns false.
+     *
+     * @param string $id OpenID identity
+     * @param string $returnTo HTTP URL to redirect response from server to
+     * @param string $root HTTP URL to identify consumer on server
+     * @param mixed $extensions extension object or array of extensions objects
+     * @param Zend_Controller_Response_Abstract $response an optional response
+     *  object to perform HTTP or HTML form redirection
+     * @return bool
+     */
+    public function check($id, $returnTo=null, $root=null, $extensions = null,
+                          Zend_Controller_Response_Abstract $response = null)
+
+    {
+        return $this->_checkId(
+            true,
+            $id,
+            $returnTo,
+            $root,
+            $extensions,
+            $response);
+    }
+
+    /**
+     * Verifies authentication response from OpenID server.
+     *
+     * This is the second step of OpenID authentication process.
+     * The function returns true on successful authentication and false on
+     * failure.
+     *
+     * @param array $params HTTP query data from OpenID server
+     * @param string &$identity this argument is set to end-user's claimed
+     *  identifier or OpenID provider local identifier.
+     * @param mixed $extensions extension object or array of extensions objects
+     * @return bool
+     */
+    public function verify($params, &$identity = "", $extensions = null)
+    {
+        $this->_setError('');
+
+        $version = 1.1;
+        if (isset($params['openid_ns']) &&
+            $params['openid_ns'] == Zend_OpenId::NS_2_0) {
+            $version = 2.0;
+        }
+
+        if (isset($params["openid_claimed_id"])) {
+            $identity = $params["openid_claimed_id"];
+        } else if (isset($params["openid_identity"])){
+            $identity = $params["openid_identity"];
+        } else {
+            $identity = "";
+        }
+
+        if ($version < 2.0 && !isset($params["openid_claimed_id"])) {
+            if ($this->_session !== null) {
+                if ($this->_session->identity === $identity) {
+                    $identity = $this->_session->claimed_id;
+                }
+            } else if (defined('SID')) {
+                if (isset($_SESSION["zend_openid"]["identity"]) &&
+                    isset($_SESSION["zend_openid"]["claimed_id"]) &&
+                    $_SESSION["zend_openid"]["identity"] === $identity) {
+                    $identity = $_SESSION["zend_openid"]["claimed_id"];
+                }
+            } else {
+                require_once "Zend/Session/Namespace.php";
+                $this->_session = new Zend_Session_Namespace("zend_openid");
+                if ($this->_session->identity === $identity) {
+                    $identity = $this->_session->claimed_id;
+                }
+            }
+        }
+
+        if (empty($params['openid_mode'])) {
+            $this->_setError("Missing openid.mode");
+            return false;
+        }
+        if (empty($params['openid_return_to'])) {
+            $this->_setError("Missing openid.return_to");
+            return false;
+        }
+        if (empty($params['openid_signed'])) {
+            $this->_setError("Missing openid.signed");
+            return false;
+        }
+        if (empty($params['openid_sig'])) {
+            $this->_setError("Missing openid.sig");
+            return false;
+        }
+        if ($params['openid_mode'] != 'id_res') {
+            $this->_setError("Wrong openid.mode '".$params['openid_mode']."' != 'id_res'");
+            return false;
+        }
+        if (empty($params['openid_assoc_handle'])) {
+            $this->_setError("Missing openid.assoc_handle");
+            return false;
+        }
+        if ($params['openid_return_to'] != Zend_OpenId::selfUrl()) {
+            /* Ignore query part in openid.return_to */
+            $pos = strpos($params['openid_return_to'], '?');
+            if ($pos === false ||
+                SUBSTR($params['openid_return_to'], 0 , $pos) != Zend_OpenId::selfUrl()) {
+
+                $this->_setError("Wrong openid.return_to '".
+                    $params['openid_return_to']."' != '" . Zend_OpenId::selfUrl() ."'");
+                return false;
+            }
+        }
+
+        if ($version >= 2.0) {
+            if (empty($params['openid_response_nonce'])) {
+                $this->_setError("Missing openid.response_nonce");
+                return false;
+            }
+            if (empty($params['openid_op_endpoint'])) {
+                $this->_setError("Missing openid.op_endpoint");
+                return false;
+            /* OpenID 2.0 (11.3) Checking the Nonce */
+            } else if (!$this->_storage->isUniqueNonce($params['openid_op_endpoint'], $params['openid_response_nonce'])) {
+                $this->_setError("Duplicate openid.response_nonce");
+                return false;
+            }
+        }
+
+
+        if (!empty($params['openid_invalidate_handle'])) {
+            if ($this->_storage->getAssociationByHandle(
+                $params['openid_invalidate_handle'],
+                $url,
+                $macFunc,
+                $secret,
+                $expires)) {
+                $this->_storage->delAssociation($url);
+            }
+        }
+
+        if ($this->_storage->getAssociationByHandle(
+                $params['openid_assoc_handle'],
+                $url,
+                $macFunc,
+                $secret,
+                $expires)) {
+            $signed = explode(',', $params['openid_signed']);
+            $data = '';
+            foreach ($signed as $key) {
+                $data .= $key . ':' . $params['openid_' . strtr($key,'.','_')] . "\n";
+            }
+            if (base64_decode($params['openid_sig']) ==
+                Zend_OpenId::hashHmac($macFunc, $data, $secret)) {
+                if (!Zend_OpenId_Extension::forAll($extensions, 'parseResponse', $params)) {
+                    $this->_setError("Extension::parseResponse failure");
+                    return false;
+                }
+                /* OpenID 2.0 (11.2) Verifying Discovered Information */
+                if (isset($params['openid_claimed_id'])) {
+                    $id = $params['openid_claimed_id'];
+                    if (!Zend_OpenId::normalize($id)) {
+                        $this->_setError("Normalization failed");
+                        return false;
+                    } else if (!$this->_discovery($id, $discovered_server, $discovered_version)) {
+                        $this->_setError("Discovery failed: " . $this->getError());
+                        return false;
+                    } else if ((!empty($params['openid_identity']) &&
+                                $params["openid_identity"] != $id) ||
+                               (!empty($params['openid_op_endpoint']) &&
+                                $params['openid_op_endpoint'] != $discovered_server) ||
+                               $discovered_version != $version) {
+                        $this->_setError("Discovery information verification failed");
+                        return false;
+                    }
+                }
+                return true;
+            }
+            $this->_storage->delAssociation($url);
+            $this->_setError("Signature check failed");
+            return false;
+        }
+        else
+        {
+            /* Use dumb mode */
+            if (isset($params['openid_claimed_id'])) {
+                $id = $params['openid_claimed_id'];
+            } else if (isset($params['openid_identity'])) {
+                $id = $params['openid_identity'];
+            } else {
+                $this->_setError("Missing openid.claimed_id and openid.identity");
+                return false;
+            }
+
+            if (!Zend_OpenId::normalize($id)) {
+                $this->_setError("Normalization failed");
+                return false;
+            } else if (!$this->_discovery($id, $server, $discovered_version)) {
+                $this->_setError("Discovery failed: " . $this->getError());
+                return false;
+            }
+
+            /* OpenID 2.0 (11.2) Verifying Discovered Information */
+            if ((isset($params['openid_identity']) &&
+                 $params["openid_identity"] != $id) ||
+                (isset($params['openid_op_endpoint']) &&
+                 $params['openid_op_endpoint'] != $server) ||
+                $discovered_version != $version) {
+                $this->_setError("Discovery information verification failed");
+                return false;
+            }
+
+            $params2 = array();
+            foreach ($params as $key => $val) {
+                if (strpos($key, 'openid_ns_') === 0) {
+                    $key = 'openid.ns.' . substr($key, strlen('openid_ns_'));
+                } else if (strpos($key, 'openid_sreg_') === 0) {
+                    $key = 'openid.sreg.' . substr($key, strlen('openid_sreg_'));
+                } else if (strpos($key, 'openid_') === 0) {
+                    $key = 'openid.' . substr($key, strlen('openid_'));
+                }
+                $params2[$key] = $val;
+            }
+            $params2['openid.mode'] = 'check_authentication';
+            $ret = $this->_httpRequest($server, 'POST', $params2, $status);
+            if ($status != 200) {
+                $this->_setError("'Dumb' signature verification HTTP request failed");
+                return false;
+            }
+            $r = array();
+            if (is_string($ret)) {
+                foreach(explode("\n", $ret) as $line) {
+                    $line = trim($line);
+                    if (!empty($line)) {
+                        $x = explode(':', $line, 2);
+                        if (is_array($x) && count($x) == 2) {
+                            list($key, $value) = $x;
+                            $r[trim($key)] = trim($value);
+                        }
+                    }
+                }
+            }
+            $ret = $r;
+            if (!empty($ret['invalidate_handle'])) {
+                if ($this->_storage->getAssociationByHandle(
+                    $ret['invalidate_handle'],
+                    $url,
+                    $macFunc,
+                    $secret,
+                    $expires)) {
+                    $this->_storage->delAssociation($url);
+                }
+            }
+            if (isset($ret['is_valid']) && $ret['is_valid'] == 'true') {
+                if (!Zend_OpenId_Extension::forAll($extensions, 'parseResponse', $params)) {
+                    $this->_setError("Extension::parseResponse failure");
+                    return false;
+                }
+                return true;
+            }
+            $this->_setError("'Dumb' signature verification failed");
+            return false;
+        }
+    }
+
+    /**
+     * Store assiciation in internal chace and external storage
+     *
+     * @param string $url OpenID server url
+     * @param string $handle association handle
+     * @param string $macFunc HMAC function (sha1 or sha256)
+     * @param string $secret shared secret
+     * @param integer $expires expiration UNIX time
+     * @return void
+     */
+    protected function _addAssociation($url, $handle, $macFunc, $secret, $expires)
+    {
+        $this->_cache[$url] = array($handle, $macFunc, $secret, $expires);
+        return $this->_storage->addAssociation(
+            $url,
+            $handle,
+            $macFunc,
+            $secret,
+            $expires);
+    }
+
+    /**
+     * Retrive assiciation information for given $url from internal cahce or
+     * external storage
+     *
+     * @param string $url OpenID server url
+     * @param string &$handle association handle
+     * @param string &$macFunc HMAC function (sha1 or sha256)
+     * @param string &$secret shared secret
+     * @param integer &$expires expiration UNIX time
+     * @return void
+     */
+    protected function _getAssociation($url, &$handle, &$macFunc, &$secret, &$expires)
+    {
+        if (isset($this->_cache[$url])) {
+            $handle   = $this->_cache[$url][0];
+            $macFunc = $this->_cache[$url][1];
+            $secret   = $this->_cache[$url][2];
+            $expires  = $this->_cache[$url][3];
+            return true;
+        }
+        if ($this->_storage->getAssociation(
+                $url,
+                $handle,
+                $macFunc,
+                $secret,
+                $expires)) {
+            $this->_cache[$url] = array($handle, $macFunc, $secret, $expires);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Performs HTTP request to given $url using given HTTP $method.
+     * Send additinal query specified by variable/value array,
+     * On success returns HTTP response without headers, false on failure.
+     *
+     * @param string $url OpenID server url
+     * @param string $method HTTP request method 'GET' or 'POST'
+     * @param array $params additional qwery parameters to be passed with
+     * @param int &$staus HTTP status code
+     *  request
+     * @return mixed
+     */
+    protected function _httpRequest($url, $method = 'GET', array $params = array(), &$status = null)
+    {
+        $client = $this->_httpClient;
+        if ($client === null) {
+            $client = new Zend_Http_Client(
+                    $url,
+                    array(
+                        'maxredirects' => 4,
+                        'timeout'      => 15,
+                        'useragent'    => 'Zend_OpenId'
+                    )
+                );
+        } else {
+            $client->setUri($url);
+        }
+
+        $client->resetParameters();
+        if ($method == 'POST') {
+            $client->setMethod(Zend_Http_Client::POST);
+            $client->setParameterPost($params);
+        } else {
+            $client->setMethod(Zend_Http_Client::GET);
+            $client->setParameterGet($params);
+        }
+
+        try {
+            $response = $client->request();
+        } catch (Exception $e) {
+            $this->_setError('HTTP Request failed: ' . $e->getMessage());
+            return false;
+        }
+        $status = $response->getStatus();
+        $body = $response->getBody();
+        if ($status == 200 || ($status == 400 && !empty($body))) {
+            return $body;
+        }else{
+            $this->_setError('Bad HTTP response');
+            return false;
+        }
+    }
+
+    /**
+     * Create (or reuse existing) association between OpenID consumer and
+     * OpenID server based on Diffie-Hellman key agreement. Returns true
+     * on success and false on failure.
+     *
+     * @param string $url OpenID server url
+     * @param float $version OpenID protocol version
+     * @param string $priv_key for testing only
+     * @return bool
+     */
+    protected function _associate($url, $version, $priv_key=null)
+    {
+
+        /* Check if we already have association in chace or storage */
+        if ($this->_getAssociation(
+                $url,
+                $handle,
+                $macFunc,
+                $secret,
+                $expires)) {
+            return true;
+        }
+
+        if ($this->_dumbMode) {
+            /* Use dumb mode */
+            return true;
+        }
+
+        $params = array();
+
+        if ($version >= 2.0) {
+            $params = array(
+                'openid.ns'           => Zend_OpenId::NS_2_0,
+                'openid.mode'         => 'associate',
+                'openid.assoc_type'   => 'HMAC-SHA256',
+                'openid.session_type' => 'DH-SHA256',
+            );
+        } else {
+            $params = array(
+                'openid.mode'         => 'associate',
+                'openid.assoc_type'   => 'HMAC-SHA1',
+                'openid.session_type' => 'DH-SHA1',
+            );
+        }
+
+        $dh = Zend_OpenId::createDhKey(pack('H*', Zend_OpenId::DH_P),
+                                       pack('H*', Zend_OpenId::DH_G),
+                                       $priv_key);
+        $dh_details = Zend_OpenId::getDhKeyDetails($dh);
+
+        $params['openid.dh_modulus']         = base64_encode(
+            Zend_OpenId::btwoc($dh_details['p']));
+        $params['openid.dh_gen']             = base64_encode(
+            Zend_OpenId::btwoc($dh_details['g']));
+        $params['openid.dh_consumer_public'] = base64_encode(
+            Zend_OpenId::btwoc($dh_details['pub_key']));
+
+        while(1) {
+            $ret = $this->_httpRequest($url, 'POST', $params, $status);
+            if ($ret === false) {
+                $this->_setError("HTTP request failed");
+                return false;
+            }
+
+            $r = array();
+            $bad_response = false;
+            foreach(explode("\n", $ret) as $line) {
+                $line = trim($line);
+                if (!empty($line)) {
+                    $x = explode(':', $line, 2);
+                    if (is_array($x) && count($x) == 2) {
+                        list($key, $value) = $x;
+                        $r[trim($key)] = trim($value);
+                    } else {
+                        $bad_response = true;
+                    }
+                }
+            }
+            if ($bad_response && strpos($ret, 'Unknown session type') !== false) {
+                $r['error_code'] = 'unsupported-type';
+            }
+            $ret = $r;
+
+            if (isset($ret['error_code']) &&
+                $ret['error_code'] == 'unsupported-type') {
+                if ($params['openid.session_type'] == 'DH-SHA256') {
+                    $params['openid.session_type'] = 'DH-SHA1';
+                    $params['openid.assoc_type'] = 'HMAC-SHA1';
+                } else if ($params['openid.session_type'] == 'DH-SHA1') {
+                    $params['openid.session_type'] = 'no-encryption';
+                } else {
+                    $this->_setError("The OpenID service responded with: " . $ret['error_code']);
+                    return false;
+                }
+            } else {
+                break;
+            }
+        }
+
+        if ($status != 200) {
+            $this->_setError("The server responded with status code: " . $status);
+            return false;
+        }
+
+        if ($version >= 2.0 &&
+            isset($ret['ns']) &&
+            $ret['ns'] != Zend_OpenId::NS_2_0) {
+            $this->_setError("Wrong namespace definition in the server response");
+            return false;
+        }
+
+        if (!isset($ret['assoc_handle']) ||
+            !isset($ret['expires_in']) ||
+            !isset($ret['assoc_type']) ||
+            $params['openid.assoc_type'] != $ret['assoc_type']) {
+            if ($params['openid.assoc_type'] != $ret['assoc_type']) {
+                $this->_setError("The returned assoc_type differed from the supplied openid.assoc_type");
+            } else {
+                $this->_setError("Missing required data from provider (assoc_handle, expires_in, assoc_type are required)");
+            }
+            return false;
+        }
+
+        $handle     = $ret['assoc_handle'];
+        $expiresIn = $ret['expires_in'];
+
+        if ($ret['assoc_type'] == 'HMAC-SHA1') {
+            $macFunc = 'sha1';
+        } else if ($ret['assoc_type'] == 'HMAC-SHA256' &&
+            $version >= 2.0) {
+            $macFunc = 'sha256';
+        } else {
+            $this->_setError("Unsupported assoc_type");
+            return false;
+        }
+
+        if ((empty($ret['session_type']) ||
+             ($version >= 2.0 && $ret['session_type'] == 'no-encryption')) &&
+             isset($ret['mac_key'])) {
+            $secret = base64_decode($ret['mac_key']);
+        } else if (isset($ret['session_type']) &&
+            $ret['session_type'] == 'DH-SHA1' &&
+            !empty($ret['dh_server_public']) &&
+            !empty($ret['enc_mac_key'])) {
+            $dhFunc = 'sha1';
+        } else if (isset($ret['session_type']) &&
+            $ret['session_type'] == 'DH-SHA256' &&
+            $version >= 2.0 &&
+            !empty($ret['dh_server_public']) &&
+            !empty($ret['enc_mac_key'])) {
+            $dhFunc = 'sha256';
+        } else {
+            $this->_setError("Unsupported session_type");
+            return false;
+        }
+        if (isset($dhFunc)) {
+            $serverPub = base64_decode($ret['dh_server_public']);
+            $dhSec = Zend_OpenId::computeDhSecret($serverPub, $dh);
+            if ($dhSec === false) {
+                $this->_setError("DH secret comutation failed");
+                return false;
+            }
+            $sec = Zend_OpenId::digest($dhFunc, $dhSec);
+            if ($sec === false) {
+                $this->_setError("Could not create digest");
+                return false;
+            }
+            $secret = $sec ^ base64_decode($ret['enc_mac_key']);
+        }
+        if ($macFunc == 'sha1') {
+            if (Zend_OpenId::strlen($secret) != 20) {
+                $this->_setError("The length of the sha1 secret must be 20");
+                return false;
+            }
+        } else if ($macFunc == 'sha256') {
+            if (Zend_OpenId::strlen($secret) != 32) {
+                $this->_setError("The length of the sha256 secret must be 32");
+                return false;
+            }
+        }
+        $this->_addAssociation(
+            $url,
+            $handle,
+            $macFunc,
+            $secret,
+            time() + $expiresIn);
+        return true;
+    }
+
+    /**
+     * Performs discovery of identity and finds OpenID URL, OpenID server URL
+     * and OpenID protocol version. Returns true on succees and false on
+     * failure.
+     *
+     * @param string &$id OpenID identity URL
+     * @param string &$server OpenID server URL
+     * @param float &$version OpenID protocol version
+     * @return bool
+     * @todo OpenID 2.0 (7.3) XRI and Yadis discovery
+     */
+    protected function _discovery(&$id, &$server, &$version)
+    {
+        $realId = $id;
+        if ($this->_storage->getDiscoveryInfo(
+                $id,
+                $realId,
+                $server,
+                $version,
+                $expire)) {
+            $id = $realId;
+            return true;
+        }
+
+        /* TODO: OpenID 2.0 (7.3) XRI and Yadis discovery */
+
+        /* HTML-based discovery */
+        $response = $this->_httpRequest($id, 'GET', array(), $status);
+        if ($status != 200 || !is_string($response)) {
+            return false;
+        }
+        if (preg_match(
+                '/<link[^>]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid2.provider[ \t]*[^"\']*\\1[^>]*href=(["\'])([^"\']+)\\2[^>]*\/?>/i',
+                $response,
+                $r)) {
+            $version = 2.0;
+            $server = $r[3];
+        } else if (preg_match(
+                '/<link[^>]*href=(["\'])([^"\']+)\\1[^>]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid2.provider[ \t]*[^"\']*\\3[^>]*\/?>/i',
+                $response,
+                $r)) {
+            $version = 2.0;
+            $server = $r[2];
+        } else if (preg_match(
+                '/<link[^>]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid.server[ \t]*[^"\']*\\1[^>]*href=(["\'])([^"\']+)\\2[^>]*\/?>/i',
+                $response,
+                $r)) {
+            $version = 1.1;
+            $server = $r[3];
+        } else if (preg_match(
+                '/<link[^>]*href=(["\'])([^"\']+)\\1[^>]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid.server[ \t]*[^"\']*\\3[^>]*\/?>/i',
+                $response,
+                $r)) {
+            $version = 1.1;
+            $server = $r[2];
+        } else {
+            return false;
+        }
+        if ($version >= 2.0) {
+            if (preg_match(
+                    '/<link[^>]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid2.local_id[ \t]*[^"\']*\\1[^>]*href=(["\'])([^"\']+)\\2[^>]*\/?>/i',
+                    $response,
+                    $r)) {
+                $realId = $r[3];
+            } else if (preg_match(
+                    '/<link[^>]*href=(["\'])([^"\']+)\\1[^>]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid2.local_id[ \t]*[^"\']*\\3[^>]*\/?>/i',
+                    $response,
+                    $r)) {
+                $realId = $r[2];
+            }
+        } else {
+            if (preg_match(
+                    '/<link[^>]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid.delegate[ \t]*[^"\']*\\1[^>]*href=(["\'])([^"\']+)\\2[^>]*\/?>/i',
+                    $response,
+                    $r)) {
+                $realId = $r[3];
+            } else if (preg_match(
+                    '/<link[^>]*href=(["\'])([^"\']+)\\1[^>]*rel=(["\'])[ \t]*(?:[^ \t"\']+[ \t]+)*?openid.delegate[ \t]*[^"\']*\\3[^>]*\/?>/i',
+                    $response,
+                    $r)) {
+                $realId = $r[2];
+            }
+        }
+
+        $expire = time() + 60 * 60;
+        $this->_storage->addDiscoveryInfo($id, $realId, $server, $version, $expire);
+        $id = $realId;
+        return true;
+    }
+
+    /**
+     * Performs check of OpenID identity.
+     *
+     * This is the first step of OpenID authentication process.
+     * On success the function does not return (it does HTTP redirection to
+     * server and exits). On failure it returns false.
+     *
+     * @param bool $immediate enables or disables interaction with user
+     * @param string $id OpenID identity
+     * @param string $returnTo HTTP URL to redirect response from server to
+     * @param string $root HTTP URL to identify consumer on server
+     * @param mixed $extensions extension object or array of extensions objects
+     * @param Zend_Controller_Response_Abstract $response an optional response
+     *  object to perform HTTP or HTML form redirection
+     * @return bool
+     */
+    protected function _checkId($immediate, $id, $returnTo=null, $root=null,
+        $extensions=null, Zend_Controller_Response_Abstract $response = null)
+    {
+        $this->_setError('');
+
+        if (!Zend_OpenId::normalize($id)) {
+            $this->_setError("Normalisation failed");
+            return false;
+        }
+        $claimedId = $id;
+
+        if (!$this->_discovery($id, $server, $version)) {
+            $this->_setError("Discovery failed: " . $this->getError());
+            return false;
+        }
+        if (!$this->_associate($server, $version)) {
+            $this->_setError("Association failed: " . $this->getError());
+            return false;
+        }
+        if (!$this->_getAssociation(
+                $server,
+                $handle,
+                $macFunc,
+                $secret,
+                $expires)) {
+            /* Use dumb mode */
+            unset($handle);
+            unset($macFunc);
+            unset($secret);
+            unset($expires);
+        }
+
+        $params = array();
+        if ($version >= 2.0) {
+            $params['openid.ns'] = Zend_OpenId::NS_2_0;
+        }
+
+        $params['openid.mode'] = $immediate ?
+            'checkid_immediate' : 'checkid_setup';
+
+        $params['openid.identity'] = $id;
+
+        $params['openid.claimed_id'] = $claimedId;
+
+        if ($version <= 2.0) {
+            if ($this->_session !== null) {
+                $this->_session->identity = $id;
+                $this->_session->claimed_id = $claimedId;
+            } else if (defined('SID')) {
+                $_SESSION["zend_openid"] = array(
+                    "identity" => $id,
+                    "claimed_id" => $claimedId);
+            } else {
+                require_once "Zend/Session/Namespace.php";
+                $this->_session = new Zend_Session_Namespace("zend_openid");
+                $this->_session->identity = $id;
+                $this->_session->claimed_id = $claimedId;
+            }
+        }
+
+        if (isset($handle)) {
+            $params['openid.assoc_handle'] = $handle;
+        }
+
+        $params['openid.return_to'] = Zend_OpenId::absoluteUrl($returnTo);
+
+        if (empty($root)) {
+            $root = Zend_OpenId::selfUrl();
+            if ($root[strlen($root)-1] != '/') {
+                $root = dirname($root);
+            }
+        }
+        if ($version >= 2.0) {
+            $params['openid.realm'] = $root;
+        } else {
+            $params['openid.trust_root'] = $root;
+        }
+
+        if (!Zend_OpenId_Extension::forAll($extensions, 'prepareRequest', $params)) {
+            $this->_setError("Extension::prepareRequest failure");
+            return false;
+        }
+
+        Zend_OpenId::redirect($server, $params, $response);
+        return true;
+    }
+
+    /**
+     * Sets HTTP client object to make HTTP requests
+     *
+     * @param Zend_Http_Client $client HTTP client object to be used
+     */
+    public function setHttpClient($client) {
+        $this->_httpClient = $client;
+    }
+
+    /**
+     * Returns HTTP client object that will be used to make HTTP requests
+     *
+     * @return Zend_Http_Client
+     */
+    public function getHttpClient() {
+        return $this->_httpClient;
+    }
+
+    /**
+     * Sets session object to store climed_id
+     *
+     * @param Zend_Session_Namespace $session HTTP client object to be used
+     */
+    public function setSession(Zend_Session_Namespace $session) {
+        $this->_session = $session;
+    }
+
+    /**
+     * Returns session object that is used to store climed_id
+     *
+     * @return Zend_Session_Namespace
+     */
+    public function getSession() {
+        return $this->_session;
+    }
+
+    /**
+     * Saves error message
+     *
+     * @param string $message error message
+     */
+    protected function _setError($message)
+    {
+        $this->_error = $message;
+    }
+
+    /**
+     * Returns error message that explains failure of login, check or verify
+     *
+     * @return string
+     */
+    public function getError()
+    {
+        return $this->_error;
+    }
+
+}