Files
DomFramework/src/Csrf.php
2022-11-25 21:21:30 +01:00

181 lines
5.5 KiB
PHP

<?php
/** DomFramework
* @package domframework
* @author Dominique Fournier <dominique@fournier38.fr>
* @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 = "<input type='hidden' name='$this->field' ";
$res .= "value='$this->csrfToken'/>\n";
return $res;
}
/** Return the token if exists or create a new one if needed
*/
public function getToken()
{
if ($this->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;
}
}