* @license BSD */ namespace Domframework; /** CSRF protection * By default, the CSRF protection is active if a SESSION is active too. * It can be disabled if needed. An Exception is raised if the form is send * back without the token */ class Csrf { /** Allow to disable the csrf protection */ public $csrf = true; /** This hidden field name in HTML */ public $field = "CSRF_TOKEN"; /** The created token */ private $csrfToken = ""; /** Timeout of the CSRF token : 3600s by default (maximum time allowed to * enter information in form and submit) */ private $csrfTimeout = 3600; /** Manage the singleton */ public function __construct() { if (isset($GLOBALS["domframework"]["csrf"])) { $this->csrfToken = $GLOBALS["domframework"]["csrf"]->csrfToken; $this->field = $GLOBALS["domframework"]["csrf"]->field; $this->csrfTimeout = $GLOBALS["domframework"]["csrf"]->csrfTimeout; } else { $GLOBALS["domframework"]["csrf"] = $this; } } /** Get / Set the status of the CSRF protection * @param boolean|null $val The value to set/get if null */ public function csrfState($val = null) { if ($val === null) { return $this->csrf; } $this->csrf = !! $val; $GLOBALS["domframework"]["csrf"] = $this; return $this; } /** Get / Set the name of the field in HTML * @param string|null $val The value to set/get if null */ public function field($val = null) { if ($val === null) { return $this->field; } $this->field = $val; $GLOBALS["domframework"]["csrf"] = $this; return $this; } /** This function return the token */ public function createToken() { $l = 30; // Number of chars in token $c = "abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ"; for ( $s = '', $cl = strlen($c) - 1, $i = 0; $i < $l; $s .= $c[mt_rand(0, $cl)], ++$i ); $this->csrfToken = $s; $_SESSION["domframework"]["csrf"][$this->csrfToken] = microtime(true); $GLOBALS["domframework"]["csrf"] = $this; return $this->csrfToken; } /** Check if the provided token is the right token, defined last displayed * page * @param string $tokenFromUser The value csrf the user's token */ public function checkToken($tokenFromUser) { if ($this->csrf === false) { return true; } // Migrate from unique format to multiple CSRF tokens format // The new format is : array (token => the last used time) if ( isset($_SESSION["domframework"]["csrf"]["csrf"]) && isset($_SESSION["domframework"]["csrf"]["csrfStart"]) ) { $_SESSION["domframework"]["csrf"] = array( $_SESSION["domframework"]["csrf"]["csrf"] => $_SESSION["domframework"]["csrf"]["csrfStart"] ); unset($_SESSION["domframework"]["csrf"]["csrfStart"]); } if (! isset($_SESSION["domframework"]["csrf"])) { throw new \Exception(dgettext( "domframework", "No previous CSRF token found in session " . "(maybe a new session after expiration ?) : abort" ), 406); } if (! key_exists($tokenFromUser, $_SESSION["domframework"]["csrf"])) { throw new \Exception(dgettext( "domframework", "Invalid CSRF token provided" ), 406); } if ( ($_SESSION["domframework"]["csrf"][$tokenFromUser] + $this->csrfTimeout) < microtime(true) ) { throw new \Exception(dgettext( "domframework", "Obsolete CSRF token provided" ), 406); } // Clean expired tokens (2 times the $this->csrfTimeout) foreach ($_SESSION["domframework"]["csrf"] as $token => $timeout) { if ($timeout + 2 * $this->csrfTimeout < time()) { unset($_SESSION["domframework"]["csrf"][$token]); } } return true; } /** Return the CSRF token in a hidden field */ public function displayFormCSRF() { if ($this->csrfToken == "") { $this->createToken(); } $res = "csrfToken === "") { $this->createToken(); } return $this->csrfToken; } /** Add more time to existing CSRF token * @param string $tokenFromUser The existing token */ public function extendToken($tokenFromUser) { $this->checkToken($tokenFromUser); $_SESSION["domframework"]["csrf"][$tokenFromUser] = microtime(true); return true; } /** Check an existing token, then delete it * @param string $tokenFromUser The existing token */ public function checkThenDeleteToken($tokenFromUser) { $this->checkToken($tokenFromUser); unset($_SESSION["domframework"]["csrf"][$tokenFromUser]); return true; } }