From e2d733d5ec8a9fc31530148818cbbe6007ad4cae Mon Sep 17 00:00:00 2001 From: Gunnar Wrobel Date: Tue, 30 Nov 2010 13:37:53 +0100 Subject: [PATCH] Add methods that throw an exception in case a token is invalid. --- framework/Token/lib/Horde/Token/Base.php | 86 +++++++++++++++-- framework/Token/lib/Horde/Token/Exception.php | 2 +- .../Token/lib/Horde/Token/Exception/Expired.php | 30 ++++++ .../Token/lib/Horde/Token/Exception/Invalid.php | 30 ++++++ framework/Token/lib/Horde/Token/Exception/Used.php | 30 ++++++ framework/Token/package.xml | 106 +++++++++++++++------ framework/Token/test/Horde/Token/Unit/FileTest.php | 52 +++++++++- 7 files changed, 297 insertions(+), 39 deletions(-) create mode 100644 framework/Token/lib/Horde/Token/Exception/Expired.php create mode 100644 framework/Token/lib/Horde/Token/Exception/Invalid.php create mode 100644 framework/Token/lib/Horde/Token/Exception/Used.php diff --git a/framework/Token/lib/Horde/Token/Base.php b/framework/Token/lib/Horde/Token/Base.php index 9cbf4334c..91b834736 100644 --- a/framework/Token/lib/Horde/Token/Base.php +++ b/framework/Token/lib/Horde/Token/Base.php @@ -137,18 +137,14 @@ abstract class Horde_Token_Base */ public function validate($token, $seed = '', $timeout = null, $unique = false) { - $b = Horde_Url::uriB64Decode($token); - $nonce = substr($b, 0, 6); - $hash = substr($b, 6); + list($nonce, $hash) = $this->_decode($token); if ($hash != $this->_hash($nonce . $seed)) { return false; } - $timestamp = unpack('N', substr($nonce, 0, 4)); - $timestamp = array_pop($timestamp); if ($timeout === null) { $timeout = $this->_params['token_lifetime']; } - if ($timeout >= 0 && (time() - $timestamp - $timeout) >= 0) { + if ($this->_isExpired($nonce, $timeout)) { return false; } if ($unique) { @@ -157,6 +153,84 @@ abstract class Horde_Token_Base return true; } + /** + * Is the given token still valid? Throws an exception in case it is not. + * + * @param string $token The signed token. + * @param string $seed The unique ID of the token. +nce? + * + * @return array An array of two elements: The nonce and the hash. + * + * @throws Horde_Token_Exception If the token was invalid. + */ + public function isValid($token, $seed = '') + { + list($nonce, $hash) = $this->_decode($token); + if ($hash != $this->_hash($nonce . $seed)) { + throw new Horde_Token_Exception_Invalid('We cannot verify that this request was really sent by you. It could be a malicious request. If you intended to perform this action, you can retry it now.'); + } + if ($this->_isExpired($nonce, $this->_params['token_lifetime'])) { + throw new Horde_Token_Exception_Expired(sprintf("This request cannot be completed because the link you followed or the form you submitted was only valid for %s minutes. Please try again now.", floor($this->_params['token_lifetime'] / 60))); + } + return array($nonce, $hash); + } + + /** + * Is the given token valid and has never been used before? Throws an + * exception otherwise. + * + * @param string $token The signed token. + * @param string $seed The unique ID of the token. +nce? + * + * @return NULL + * + * @throws Horde_Token_Exception If the token was invalid or has been used before. + */ + public function isValidAndUnused($token, $seed = '') + { + list($nonce, $hash) = $this->isValid($token, $seed); + if (!$this->verify($nonce)) { + throw new Horde_Token_Exception_Used('This token has been used before!'); + } + } + + /** + * Decode a token into the prefixed nonce and the hash. + * + * @param string $token The token to be decomposed. + * + * @return array An array of two elements: The nonce and the hash. + */ + private function _decode($token) + { + $b = Horde_Url::uriB64Decode($token); + return array(substr($b, 0, 6), substr($b, 6)); + } + + /** + * Has the nonce expired? + * + * @param string $nonce The to be checked for expiration. + * @param int $timeout The timeout that should be applied. + * + * @return boolean True if the nonce expired. + */ + private function _isExpired($nonce, $timeout) + { + $timestamp = unpack('N', substr($nonce, 0, 4)); + $timestamp = array_pop($timestamp); + return $timeout >= 0 && (time() - $timestamp - $timeout) >= 0; + } + + /** + * Sign the given text with the secret. + * + * @param string $text The text to be signed. + * + * @return string The hashed text. + */ private function _hash($text) { return hash('sha256', $text . $this->_params['secret'], true); diff --git a/framework/Token/lib/Horde/Token/Exception.php b/framework/Token/lib/Horde/Token/Exception.php index 97cedc079..f7bde4020 100644 --- a/framework/Token/lib/Horde/Token/Exception.php +++ b/framework/Token/lib/Horde/Token/Exception.php @@ -11,6 +11,6 @@ * @category Horde * @package Token */ -class Horde_Token_Exception extends Horde_Exception_Prior +class Horde_Token_Exception extends Horde_Exception { } diff --git a/framework/Token/lib/Horde/Token/Exception/Expired.php b/framework/Token/lib/Horde/Token/Exception/Expired.php new file mode 100644 index 000000000..dec3b9a6a --- /dev/null +++ b/framework/Token/lib/Horde/Token/Exception/Expired.php @@ -0,0 +1,30 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Token + */ + +/** + * Indicates an expired token. + * + * Copyright 2010 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Horde + * @package Token + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Token + */ +class Horde_Token_Exception_Expired extends Horde_Token_Exception +{ +} diff --git a/framework/Token/lib/Horde/Token/Exception/Invalid.php b/framework/Token/lib/Horde/Token/Exception/Invalid.php new file mode 100644 index 000000000..1357ef140 --- /dev/null +++ b/framework/Token/lib/Horde/Token/Exception/Invalid.php @@ -0,0 +1,30 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Token + */ + +/** + * Indicates an invalid token. + * + * Copyright 2010 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Horde + * @package Token + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Token + */ +class Horde_Token_Exception_Invalid extends Horde_Token_Exception +{ +} diff --git a/framework/Token/lib/Horde/Token/Exception/Used.php b/framework/Token/lib/Horde/Token/Exception/Used.php new file mode 100644 index 000000000..f4f319083 --- /dev/null +++ b/framework/Token/lib/Horde/Token/Exception/Used.php @@ -0,0 +1,30 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Token + */ + +/** + * Indicates a used token. + * + * Copyright 2010 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Horde + * @package Token + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Token + */ +class Horde_Token_Exception_Used extends Horde_Token_Exception +{ +} diff --git a/framework/Token/package.xml b/framework/Token/package.xml index 2b1614744..d1a7063c8 100644 --- a/framework/Token/package.xml +++ b/framework/Token/package.xml @@ -1,22 +1,19 @@ - + Token pear.horde.org Horde Token API The Horde_Token:: class provides a common abstracted interface into the various token generation mediums. It also includes all of the - functions for retrieving, storing, and checking tokens. - + functions for retrieving, storing, and checking tokens. Chuck Hagenbuch chuck chuck@horde.org yes - 2009-02-21 + 2010-11-30 + 0.1.0 0.1.0 @@ -26,16 +23,22 @@ http://pear.php.net/dtd/package-2.0.xsd"> beta LGPL - * Add Horde_Token_Exception::. + +* Add Horde_Token_Exception::. * Move driver code into sub-classes. * Use exceptions, not PEAR_Errors. * Initial Horde 4 package. - + + + + + + @@ -45,6 +48,18 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + + + + + + + + + + @@ -81,28 +96,50 @@ http://pear.php.net/dtd/package-2.0.xsd"> - - - - - - + + + + + + + + + + + + + - 0.0.4 - 0.0.4 + 0.0.1 + 0.0.1 + + + alpha + alpha + + 2003-07-03 + LGPL + +Initial release as a PEAR package + + + + + 0.0.2 + 0.0.2 beta beta - 2006-05-08 - + 2004-11-23 LGPL - Converted to package.xml 2.0 for pear.horde.org + +Remove assumption of IPv4 IP address format @@ -116,35 +153,42 @@ http://pear.php.net/dtd/package-2.0.xsd"> 2005-01-01 LGPL - * Add support for separate read and write DB servers for the sql driver. + +* Add support for separate read and write DB servers for the sql driver. - 0.0.2 - 0.0.2 + 0.0.4 + 0.0.4 beta beta - 2004-11-23 + 2006-05-08 + LGPL - Remove assumption of IPv4 IP address format + +Converted to package.xml 2.0 for pear.horde.org - 0.0.1 - 0.0.1 + 0.1.0 + 0.1.0 - alpha - alpha + beta + beta - 2003-07-03 + 2010-11-30 LGPL - Initial release as a PEAR package + +* Add Horde_Token_Exception::. + * Move driver code into sub-classes. + * Use exceptions, not PEAR_Errors. + * Initial Horde 4 package. diff --git a/framework/Token/test/Horde/Token/Unit/FileTest.php b/framework/Token/test/Horde/Token/Unit/FileTest.php index 81929f3e6..e0d93c8e5 100644 --- a/framework/Token/test/Horde/Token/Unit/FileTest.php +++ b/framework/Token/test/Horde/Token/Unit/FileTest.php @@ -17,7 +17,7 @@ require_once dirname(__FILE__) . '/../Autoload.php'; /** - * Base for session testing. + * Test the file based token backend. * * Copyright 2010 The Horde Project (http://www.horde.org/) * @@ -133,6 +133,56 @@ class Horde_Token_Unit_FileTest extends PHPUnit_Framework_TestCase } /** + * @expectedException Horde_Token_Exception_Invalid + */ + public function testInvalidTokenException() + { + $t = new Horde_Token_File(array('secret' => 'abc')); + $t->isValid('something'); + } + + /** + * @expectedException Horde_Token_Exception_Invalid + */ + public function testInvalidSeedException() + { + $t = new Horde_Token_File(array('secret' => 'abc')); + $t->isValid($t->get('a'), 'b'); + } + + /** + * @expectedException Horde_Token_Exception_Expired + */ + public function testTimeoutException() + { + $t = new Horde_Token_File( + array( + 'secret' => 'abc', + 'token_lifetime' => 1 + ) + ); + $token = $t->get('a'); + sleep(1); + $t->isValid($token, 'a'); + } + + /** + * @expectedException Horde_Token_Exception_Used + */ + public function testIsValidAndUnusedException() + { + $t = new Horde_Token_File( + array( + 'secret' => 'abc', + 'token_dir' => $this->_getTemporaryDirectory() + ) + ); + $token = $t->get('a'); + $t->isValidAndUnused($token, 'a'); + $t->isValidAndUnused($token, 'a'); + } + + /** * @expectedException Horde_Token_Exception */ public function testInvalidConstruction() -- 2.11.0