2019-07-17 20:08:50 +00:00
|
|
|
<?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
|
2019-07-17 20:19:00 +00:00
|
|
|
* @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
|
2019-07-17 20:08:50 +00:00
|
|
|
* @license http://framework.zend.com/license/new-bsd New BSD License
|
2019-07-17 20:19:00 +00:00
|
|
|
* @version $Id: Consumer.php 16212 2009-06-21 19:24:49Z thomas $
|
2019-07-17 20:08:50 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @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
|
2019-07-17 20:19:00 +00:00
|
|
|
* @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
|
2019-07-17 20:08:50 +00:00
|
|
|
* @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"])) {
|
2019-07-17 20:16:19 +00:00
|
|
|
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 {
|
2019-07-17 20:08:50 +00:00
|
|
|
require_once "Zend/Session/Namespace.php";
|
|
|
|
$this->_session = new Zend_Session_Namespace("zend_openid");
|
2019-07-17 20:16:19 +00:00
|
|
|
if ($this->_session->identity === $identity) {
|
|
|
|
$identity = $this->_session->claimed_id;
|
|
|
|
}
|
2019-07-17 20:08:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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()) {
|
2019-07-17 20:16:19 +00:00
|
|
|
|
2019-07-17 20:08:50 +00:00
|
|
|
$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)) {
|
2019-07-17 20:16:19 +00:00
|
|
|
$this->_setError("Extension::parseResponse failure");
|
2019-07-17 20:08:50 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
/* OpenID 2.0 (11.2) Verifying Discovered Information */
|
|
|
|
if (isset($params['openid_claimed_id'])) {
|
|
|
|
$id = $params['openid_claimed_id'];
|
2019-07-17 20:16:19 +00:00
|
|
|
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) {
|
2019-07-17 20:08:50 +00:00
|
|
|
$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 {
|
2019-07-17 20:16:19 +00:00
|
|
|
$this->_setError("Missing openid.claimed_id and openid.identity");
|
2019-07-17 20:08:50 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-07-17 20:16:19 +00:00
|
|
|
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());
|
2019-07-17 20:08:50 +00:00
|
|
|
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) {
|
2019-07-17 20:16:19 +00:00
|
|
|
$this->_setError("'Dumb' signature verification HTTP request failed");
|
2019-07-17 20:08:50 +00:00
|
|
|
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) {
|
2019-07-17 20:16:19 +00:00
|
|
|
$this->_setError("HTTP request failed");
|
2019-07-17 20:08:50 +00:00
|
|
|
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 {
|
2019-07-17 20:16:19 +00:00
|
|
|
$this->_setError("The OpenID service responded with: " . $ret['error_code']);
|
2019-07-17 20:08:50 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($status != 200) {
|
2019-07-17 20:16:19 +00:00
|
|
|
$this->_setError("The server responded with status code: " . $status);
|
2019-07-17 20:08:50 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($version >= 2.0 &&
|
|
|
|
isset($ret['ns']) &&
|
|
|
|
$ret['ns'] != Zend_OpenId::NS_2_0) {
|
2019-07-17 20:16:19 +00:00
|
|
|
$this->_setError("Wrong namespace definition in the server response");
|
2019-07-17 20:08:50 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isset($ret['assoc_handle']) ||
|
|
|
|
!isset($ret['expires_in']) ||
|
|
|
|
!isset($ret['assoc_type']) ||
|
|
|
|
$params['openid.assoc_type'] != $ret['assoc_type']) {
|
2019-07-17 20:16:19 +00:00
|
|
|
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)");
|
|
|
|
}
|
2019-07-17 20:08:50 +00:00
|
|
|
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 {
|
2019-07-17 20:16:19 +00:00
|
|
|
$this->_setError("Unsupported assoc_type");
|
2019-07-17 20:08:50 +00:00
|
|
|
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 {
|
2019-07-17 20:16:19 +00:00
|
|
|
$this->_setError("Unsupported session_type");
|
2019-07-17 20:08:50 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (isset($dhFunc)) {
|
|
|
|
$serverPub = base64_decode($ret['dh_server_public']);
|
|
|
|
$dhSec = Zend_OpenId::computeDhSecret($serverPub, $dh);
|
|
|
|
if ($dhSec === false) {
|
2019-07-17 20:16:19 +00:00
|
|
|
$this->_setError("DH secret comutation failed");
|
2019-07-17 20:08:50 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
$sec = Zend_OpenId::digest($dhFunc, $dhSec);
|
|
|
|
if ($sec === false) {
|
2019-07-17 20:16:19 +00:00
|
|
|
$this->_setError("Could not create digest");
|
2019-07-17 20:08:50 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
$secret = $sec ^ base64_decode($ret['enc_mac_key']);
|
|
|
|
}
|
|
|
|
if ($macFunc == 'sha1') {
|
2019-07-17 20:16:19 +00:00
|
|
|
if (Zend_OpenId::strlen($secret) != 20) {
|
|
|
|
$this->_setError("The length of the sha1 secret must be 20");
|
2019-07-17 20:08:50 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else if ($macFunc == 'sha256') {
|
2019-07-17 20:16:19 +00:00
|
|
|
if (Zend_OpenId::strlen($secret) != 32) {
|
|
|
|
$this->_setError("The length of the sha256 secret must be 32");
|
2019-07-17 20:08:50 +00:00
|
|
|
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
|
2019-07-17 20:16:19 +00:00
|
|
|
* @todo OpenID 2.0 (7.3) XRI and Yadis discovery
|
2019-07-17 20:08:50 +00:00
|
|
|
*/
|
|
|
|
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)) {
|
2019-07-17 20:16:19 +00:00
|
|
|
$this->_setError("Discovery failed: " . $this->getError());
|
2019-07-17 20:08:50 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (!$this->_associate($server, $version)) {
|
2019-07-17 20:16:19 +00:00
|
|
|
$this->_setError("Association failed: " . $this->getError());
|
2019-07-17 20:08:50 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2019-07-17 20:16:19 +00:00
|
|
|
$params['openid.mode'] = $immediate ?
|
2019-07-17 20:08:50 +00:00
|
|
|
'checkid_immediate' : 'checkid_setup';
|
|
|
|
|
|
|
|
$params['openid.identity'] = $id;
|
|
|
|
|
|
|
|
$params['openid.claimed_id'] = $claimedId;
|
|
|
|
|
|
|
|
if ($version <= 2.0) {
|
2019-07-17 20:16:19 +00:00
|
|
|
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 {
|
2019-07-17 20:08:50 +00:00
|
|
|
require_once "Zend/Session/Namespace.php";
|
|
|
|
$this->_session = new Zend_Session_Namespace("zend_openid");
|
2019-07-17 20:16:19 +00:00
|
|
|
$this->_session->identity = $id;
|
|
|
|
$this->_session->claimed_id = $claimedId;
|
2019-07-17 20:08:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|