Overview

Packages

  • Token

Classes

  • Horde_Token
  • Horde_Token_Base
  • Horde_Token_Exception
  • Horde_Token_Exception_Expired
  • Horde_Token_Exception_Invalid
  • Horde_Token_Exception_Used
  • Horde_Token_File
  • Horde_Token_Null
  • Horde_Token_Sql
  • Horde_Token_Translation
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * The Horde_Token_Base:: class provides a common abstracted interface for
  4:  * a token implementation.
  5:  *
  6:  * Copyright 2010-2012 Horde LLC (http://www.horde.org/)
  7:  *
  8:  * See the enclosed file COPYING for license information (LGPL). If you
  9:  * did not receive this file, see http://www.horde.org/licenses/lgpl21.
 10:  *
 11:  * @author   Max Kalika <max@horde.org>
 12:  * @author   Chuck Hagenbuch <chuck@horde.org>
 13:  * @category Horde
 14:  * @package  Token
 15:  */
 16: abstract class Horde_Token_Base
 17: {
 18:     /**
 19:      * Hash of parameters necessary to use the chosen backend.
 20:      *
 21:      * @var array
 22:      */
 23:     protected $_params = array();
 24: 
 25:     /**
 26:      * Constructor.
 27:      *
 28:      * @param array $params  Required parameters:
 29:      * - secret (string): The secret string used for signing tokens.
 30:      * Optional parameters:
 31:      * - token_lifetime (integer): The number of seconds after which tokens
 32:      *                             time out. Negative numbers represent "no
 33:      *                             timeout". The default is "-1".
 34:      * - timeout (integer): The period (in seconds) after which an id is purged.
 35:      *                      DEFAULT: 86400 (24 hours)
 36:      */
 37:     public function __construct($params)
 38:     {
 39:         if (!isset($params['secret'])) {
 40:             throw new Horde_Token_Exception('Missing secret parameter.');
 41:         }
 42: 
 43:         $params = array_merge(array(
 44:             'token_lifetime' => -1,
 45:             'timeout' => 86400
 46:         ), $params);
 47: 
 48:         $this->_params = $params;
 49:     }
 50: 
 51:     /**
 52:      * Checks if the given token has been previously used. First
 53:      * purges all expired tokens. Then retrieves current tokens for
 54:      * the given ip address. If the specified token was not found,
 55:      * adds it.
 56:      *
 57:      * @param string $token  The value of the token to check.
 58:      *
 59:      * @return boolean  True if the token has not been used, false otherwise.
 60:      * @throws Horde_Token_Exception
 61:      */
 62:     public function verify($token)
 63:     {
 64:         $this->purge();
 65: 
 66:         if ($this->exists($token)) {
 67:             return false;
 68:         }
 69: 
 70:         $this->add($token);
 71:         return true;
 72:     }
 73: 
 74:     /**
 75:      * Does the token exist?
 76:      *
 77:      * @param string $tokenID  Token ID.
 78:      *
 79:      * @return boolean  True if the token exists.
 80:      * @throws Horde_Token_Exception
 81:      */
 82:     abstract public function exists($tokenID);
 83: 
 84:     /**
 85:      * Add a token ID.
 86:      *
 87:      * @param string $tokenID  Token ID to add.
 88:      *
 89:      * @throws Horde_Token_Exception
 90:      */
 91:     abstract public function add($tokenID);
 92: 
 93:     /**
 94:      * Delete all expired connection IDs.
 95:      *
 96:      * @throws Horde_Token_Exception
 97:      */
 98:     abstract public function purge();
 99: 
100:     /**
101:      * Return a new signed token.
102:      *
103:      * @param string $seed  A unique ID to be included in the token.
104:      *
105:      * @return string The new token.
106:      */
107:     public function get($seed = '')
108:     {
109:         $nonce = $this->getNonce();
110:         return Horde_Url::uriB64Encode(
111:             $nonce . $this->_hash($nonce . $seed)
112:         );
113:     }
114: 
115:     /**
116:      * Validate a signed token.
117:      *
118:      * @param string  $token    The signed token.
119:      * @param string  $seed     The unique ID of the token.
120:      * @param int     $timeout  Timout of the token in seconds.
121:      *                          Values below zero represent no timeout.
122:      * @param boolean $unique   Should validation of the token succeed only once?
123:      *
124:      * @return boolean  True if the token was valid.
125:      */
126:     public function isValid($token, $seed = '', $timeout = null,
127:                             $unique = false)
128:     {
129:         list($nonce, $hash) = $this->_decode($token);
130:         if ($hash != $this->_hash($nonce . $seed)) {
131:             return false;
132:         }
133:         if ($timeout === null) {
134:             $timeout = $this->_params['token_lifetime'];
135:         }
136:         if ($this->_isExpired($nonce, $timeout)) {
137:             return false;
138:         }
139:         if ($unique) {
140:             return $this->verify($token);
141:         }
142:         return true;
143:     }
144: 
145:     /**
146:      * Is the given token still valid? Throws an exception in case it is not.
147:      *
148:      * @param string  $token    The signed token.
149:      * @param string  $seed     The unique ID of the token.
150:      * @param int     $timeout  Timout of the token in seconds.
151:      *                          Values below zero represent no timeout.
152:      *
153:      * @return array An array of two elements: The nonce and the hash.
154:      *
155:      * @throws Horde_Token_Exception If the token was invalid.
156:      */
157:     public function validate($token, $seed = '', $timeout = null)
158:     {
159:         list($nonce, $hash) = $this->_decode($token);
160:         if ($hash != $this->_hash($nonce . $seed)) {
161:             throw new Horde_Token_Exception_Invalid(Horde_Token_Translation::t('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.'));
162:         }
163:         if ($timeout === null) {
164:             $timeout = $this->_params['token_lifetime'];
165:         }
166:         if ($this->_isExpired($nonce, $timeout)) {
167:             throw new Horde_Token_Exception_Expired(sprintf(Horde_Token_Translation::t("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($timeout / 60)));
168:         }
169:         return array($nonce, $hash);
170:     }
171: 
172:     /**
173:      * Is the given token valid and has never been used before? Throws an
174:      * exception otherwise.
175:      *
176:      * @param string  $token  The signed token.
177:      * @param string  $seed   The unique ID of the token.
178:      *
179:      * @return NULL
180:      *
181:      * @throws Horde_Token_Exception  If the token was invalid or has been
182:      *                                used before.
183:      */
184:     public function validateUnique($token, $seed = '')
185:     {
186:         if (!$this->isValid($token, $seed)) {
187:             throw new Horde_Token_Exception_Used(Horde_Token_Translation::t('This token is invalid!'));
188:         }
189: 
190:         if (!$this->verify($token)) {
191:             throw new Horde_Token_Exception_Used(Horde_Token_Translation::t('This token has been used before!'));
192:         }
193:     }
194: 
195:     /**
196:      * Decode a token into the prefixed nonce and the hash.
197:      *
198:      * @param string $token The token to be decomposed.
199:      *
200:      * @return array An array of two elements: The nonce and the hash.
201:      */
202:     private function _decode($token)
203:     {
204:         $b = Horde_Url::uriB64Decode($token);
205:         return array(substr($b, 0, 6), substr($b, 6));
206:     }
207: 
208:     /**
209:      * Has the nonce expired?
210:      *
211:      * @param string $nonce   The to be checked for expiration.
212:      * @param int    $timeout The timeout that should be applied.
213:      *
214:      * @return boolean True if the nonce expired.
215:      */
216:     private function _isExpired($nonce, $timeout)
217:     {
218:         $timestamp = unpack('N', substr($nonce, 0, 4));
219:         $timestamp = array_pop($timestamp);
220:         return $timeout >= 0 && (time() - $timestamp - $timeout) >= 0;
221:     }
222: 
223:     /**
224:      * Sign the given text with the secret.
225:      *
226:      * @param string $text The text to be signed.
227:      *
228:      * @return string The hashed text.
229:      */
230:     private function _hash($text)
231:     {
232:         return hash('sha256', $text . $this->_params['secret'], true);
233:     }
234: 
235:     /**
236:      * Return a "number used once" (a concatenation of a timestamp and a random
237:      * numer).
238:      *
239:      * @return string A string of 6 bytes.
240:      */
241:     public function getNonce()
242:     {
243:         return pack('Nn', time(), mt_rand());
244:     }
245: 
246:     /**
247:      * Encodes the remote address.
248:      *
249:      * @return string  Encoded address.
250:      */
251:     protected function _encodeRemoteAddress()
252:     {
253:         return isset($_SERVER['REMOTE_ADDR'])
254:             ? base64_encode($_SERVER['REMOTE_ADDR'])
255:             : '';
256:     }
257: }
258: 
API documentation generated by ApiGen