TYPO3 API  SVNRelease
class.t3lib_formprotection_abstract.php
Go to the documentation of this file.
00001 <?php
00002 /***************************************************************
00003  * Copyright notice
00004  *
00005  * (c) 2010-2011 Oliver Klee <typo3-coding@oliverklee.de>
00006  * All rights reserved
00007  *
00008  * This script is part of the TYPO3 project. The TYPO3 project is
00009  * free software; you can redistribute it and/or modify
00010  * it under the terms of the GNU General Public License as published by
00011  * the Free Software Foundation; either version 2 of the License, or
00012  * (at your option) any later version.
00013  *
00014  * The GNU General Public License can be found at
00015  * http://www.gnu.org/copyleft/gpl.html.
00016  *
00017  * This script is distributed in the hope that it will be useful,
00018  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00019  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00020  * GNU General Public License for more details.
00021  *
00022  * This copyright notice MUST APPEAR in all copies of the script!
00023  ***************************************************************/
00024 
00025 /**
00026  * Class t3lib_formprotection_Abstract.
00027  *
00028  * This class provides protection against cross-site request forgery (XSRF/CSRF)
00029  * for forms.
00030  *
00031  * For documentation on how to use this class, please see the documentation of
00032  * the corresponding subclasses, e.g. t3lib_formprotection_BackendFormProtection.
00033  *
00034  * $Id$
00035  *
00036  * @package TYPO3
00037  * @subpackage t3lib
00038  *
00039  * @author Oliver Klee <typo3-coding@oliverklee.de>
00040  */
00041 abstract class t3lib_formprotection_Abstract {
00042     /**
00043      * the maximum number of tokens that can exist at the same time
00044      *
00045      * @var integer
00046      */
00047     protected $maximumNumberOfTokens = 0;
00048 
00049     /**
00050      * Valid tokens sorted from oldest to newest.
00051      *
00052      * [tokenId] => array(formName, formInstanceName)
00053      *
00054      * @var array<array>
00055      */
00056     protected $tokens = array();
00057 
00058     /**
00059      * Tokens that have been added during this request.
00060      *
00061      * @var array<array>
00062      */
00063     protected $addedTokens = array();
00064 
00065     /**
00066      * Token ids of tokens that have been dropped during this request.
00067      *
00068      * @var array
00069      */
00070     protected $droppedTokenIds = array();
00071 
00072     /**
00073      * Constructor. Makes sure existing tokens are read and available for
00074      * checking.
00075      */
00076     public function __construct() {
00077         $this->tokens = $this->retrieveTokens();
00078     }
00079 
00080     /**
00081      * Frees as much memory as possible.
00082      */
00083     public function __destruct() {
00084         $this->tokens = array();
00085     }
00086 
00087     /**
00088      * Deletes all existing tokens and persists the (empty) token table.
00089      *
00090      * This function is intended to be called when a user logs on or off.
00091      *
00092      * @return void
00093      */
00094     public function clean() {
00095         $this->tokens = array();
00096         $this->persistTokens();
00097     }
00098 
00099     /**
00100      * Generates and stores a token for a form.
00101      *
00102      * Calling this function two times with the same parameters will create
00103      * two valid, different tokens.
00104      *
00105      * Generating more tokens than $maximumNumberOfEntries will cause the oldest
00106      * tokens to get dropped.
00107      *
00108      * Note: This function does not persist the tokens.
00109      *
00110      * @param string $formName
00111      *      the name of the form, for example a table name like "tt_content",
00112      *      or some other identifier like "install_tool_password", must not be
00113      *      empty
00114      * @param string $action
00115      *      the name of the action of the form, for example "new", "delete" or
00116      *      "edit", may also be empty
00117      * @param string $formInstanceName
00118      *      a string used to differentiate two instances of the same form,
00119      *      form example a record UID or a comma-separated list of UIDs,
00120      *      may also be empty
00121      *
00122      * @return string the 32-character hex ID of the generated token
00123      */
00124     public function generateToken(
00125         $formName, $action = '', $formInstanceName = ''
00126     ) {
00127         if ($formName == '') {
00128             throw new InvalidArgumentException('$formName must not be empty.');
00129         }
00130 
00131         do {
00132             $tokenId = bin2hex(t3lib_div::generateRandomBytes(16));
00133         } while (isset($this->tokens[$tokenId]));
00134 
00135         $this->tokens[$tokenId] = array(
00136             'formName' => $formName,
00137             'action' => $action,
00138             'formInstanceName' => $formInstanceName,
00139         );
00140         $this->addedTokens[$tokenId] = $this->tokens[$tokenId];
00141         $this->preventOverflow();
00142 
00143         return $tokenId;
00144     }
00145 
00146     /**
00147      * Checks whether the token $tokenId is valid in the form $formName with
00148      * $formInstanceName.
00149      *
00150      * A token is valid if $tokenId, $formName and $formInstanceName match and
00151      * the token has not been used yet.
00152      *
00153      * Calling this function will mark the token $tokenId as invalud (if it
00154      * exists).
00155      *
00156      * So calling this function with the same parameters two times will return
00157      * FALSE the second time.
00158      *
00159      * @param string $tokenId
00160      *      a form token to check, may also be empty or utterly misformed
00161      * @param string $formName
00162      *      the name of the form to check, for example "tt_content",
00163      *      may also be empty or utterly misformed
00164      * @param string $action
00165      *      the action of the form to check, for example "edit",
00166      *      may also be empty or utterly misformed
00167      * @param string $formInstanceName
00168      *      the instance name of the form to check, for example "42" or "foo"
00169      *      or "31,42", may also be empty or utterly misformed
00170      *
00171      * @return boolean
00172      *       TRUE if $tokenId, $formName, $action and $formInstanceName match
00173      *       and the token has not been used yet, FALSE otherwise
00174      */
00175     public function validateToken(
00176         $tokenId, $formName, $action = '', $formInstanceName = ''
00177     ) {
00178         if (isset($this->tokens[$tokenId])) {
00179             $token = $this->tokens[$tokenId];
00180             $isValid = ($token['formName'] == $formName)
00181                        && ($token['action'] == $action)
00182                        && ($token['formInstanceName'] == $formInstanceName);
00183             $this->dropToken($tokenId);
00184         } else {
00185             $isValid = FALSE;
00186         }
00187 
00188         if (!$isValid) {
00189             $this->createValidationErrorMessage();
00190         }
00191 
00192         return $isValid;
00193     }
00194 
00195     /**
00196      * Creates or displayes an error message telling the user that the submitted
00197      * form token is invalid.
00198      *
00199      * This function may also be empty if the validation error should be handled
00200      * silently.
00201      *
00202      * @return void
00203      */
00204     abstract protected function createValidationErrorMessage();
00205 
00206     /**
00207      * Retrieves all saved tokens.
00208      *
00209      * @return array<arrray>
00210      *       the saved tokens, will be empty if no tokens have been saved
00211      */
00212     abstract protected function retrieveTokens();
00213 
00214     /**
00215      * Saves the tokens so that they can be used by a later incarnation of this
00216      * class.
00217      *
00218      * @return void
00219      */
00220     abstract public function persistTokens();
00221 
00222     /**
00223      * Drops the token with the ID $tokenId.
00224      *
00225      * If there is no token with that ID, this function is a no-op.
00226      *
00227      * Note: This function does not persist the tokens.
00228      *
00229      * @param string $tokenId
00230      *      the 32-character ID of an existing token, must not be empty
00231      *
00232      * @return void
00233      */
00234     protected function dropToken($tokenId) {
00235         if (isset($this->tokens[$tokenId])) {
00236             unset($this->tokens[$tokenId]);
00237             $this->droppedTokenIds[] = $tokenId;
00238         }
00239     }
00240 
00241     /**
00242      * Persisting of tokens is only required, if tokens are
00243      * deleted or added during this request.
00244      *
00245      * @return boolean
00246      */
00247     protected function isPersistingRequired() {
00248         return !empty($this->droppedTokenIds) || !empty($this->addedTokens);
00249     }
00250 
00251     /**
00252      * Reset the arrays of added or deleted tokens.
00253      *
00254      * @return void
00255      */
00256     protected function resetPersistingRequiredStatus() {
00257         $this->droppedTokenIds = array();
00258         $this->addedTokens = array();
00259     }
00260 
00261     /**
00262      * Checks whether the number of current tokens still is at most
00263      * $this->maximumNumberOfTokens.
00264      *
00265      * If there are more tokens, the oldest tokens are removed until the number
00266      * of tokens is low enough.
00267      *
00268      * Note: This function does not persist the tokens.
00269      *
00270      * @return void
00271      */
00272     protected function preventOverflow() {
00273         if (empty($this->tokens)) {
00274             return;
00275         }
00276 
00277         while (count($this->tokens) > $this->maximumNumberOfTokens) {
00278             reset($this->tokens);
00279             $this->dropToken(key($this->tokens));
00280         }
00281     }
00282 }
00283 
00284 ?>