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