You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
146 lines
3.8 KiB
146 lines
3.8 KiB
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/*
|
|
* @author Robert Strutts
|
|
* @copyright (c) 2026, Robert Strutts
|
|
* @license MIT
|
|
*/
|
|
|
|
namespace IOcornerstone\Framework\Trait\Security;
|
|
|
|
use IOcornerstone\Framework\Configure;
|
|
|
|
trait CsrfTokenFunctions
|
|
{
|
|
|
|
/**
|
|
* Set Session to use CSRF Token
|
|
* Useful for JSON data...
|
|
* @return string CSRF Token
|
|
*/
|
|
public static function createCsrfToken(): string
|
|
{
|
|
self::setup();
|
|
|
|
$newToken = self::csrfToken();
|
|
$_SESSION['csrf_pool'][$newToken] = time();
|
|
|
|
return $newToken;
|
|
}
|
|
|
|
/**
|
|
* Keep only last 15 tokens to prevent memory issues.
|
|
*/
|
|
public static function cleanUp(){
|
|
$keepOnly = 15;
|
|
if (count($_SESSION['csrf_pool']) > $keepOnly) {
|
|
$_SESSION['csrf_pool'] = array_slice($_SESSION['csrf_pool'], -$keepOnly, null, true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get CSRF Token for use with HTML Form
|
|
* @return string Hidden Form with token set
|
|
*/
|
|
public static function csrfTokenTag(): string
|
|
{
|
|
$token = self::createCsrfToken();
|
|
return "<input type=\"hidden\" id=\"csrf_token\" name=\"csrf_token\" value=\"" . $token . "\">";
|
|
}
|
|
|
|
public static function csrfTokenStillValid(string $csrfTokenKeyName = ""): bool
|
|
{
|
|
if (empty($csrfTokenKeyName)) {
|
|
$csrfTokenKeyName = $_POST['csrf_token'] ?? "";
|
|
}
|
|
|
|
$validTimeStamp = self::csrfTokenIsValid($csrfTokenKeyName);
|
|
if ($validTimeStamp === false) {
|
|
return false;
|
|
}
|
|
$recent = self::csrfTokenIsRecent($validTimeStamp);
|
|
|
|
self::destroyCsrfToken($csrfTokenKeyName); // Done, so clean up Consume token
|
|
|
|
return $recent;
|
|
}
|
|
|
|
/**
|
|
* Get an Cross-Site Request Forge - Prevention Token
|
|
* @return string
|
|
*/
|
|
private static function csrfToken(): string
|
|
{
|
|
return self::getUniqueId();
|
|
}
|
|
|
|
private static function setup(): void
|
|
{
|
|
$clean_ts = intval(Configure::get(
|
|
'security',
|
|
'token_life'
|
|
));
|
|
|
|
if ($clean_ts < 60) {
|
|
$clean_ts = 3600;
|
|
}
|
|
|
|
if (!isset($_SESSION['csrf_pool'])) {
|
|
$_SESSION['csrf_pool'] = [];
|
|
}
|
|
|
|
// Clean old tokens by useage time token_life
|
|
foreach ($_SESSION['csrf_pool'] as $key => $timestamp) {
|
|
if ($timestamp < (time() - $clean_ts)) {
|
|
unset($_SESSION['csrf_pool'][$key]);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static function csrfTokenIsValid(string $csrfTokenKeyName = ""): false|int
|
|
{
|
|
if (empty($csrfTokenKeyName)) {
|
|
$csrfTokenKeyName = $_POST['csrf_token'] ?? "";
|
|
}
|
|
|
|
if (!isset($_SESSION['csrf_pool'][$csrfTokenKeyName])) {
|
|
return false;
|
|
}
|
|
$tsToken = $_SESSION['csrf_pool'][$csrfTokenKeyName];
|
|
if (is_int($tsToken)){
|
|
return $tsToken;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private static function csrfTokenIsRecent($tokenTimeStored): bool
|
|
{
|
|
$max_elapsed = intval(Configure::get(
|
|
'security',
|
|
'max_token_age'
|
|
));
|
|
if ($max_elapsed < 30) {
|
|
$max_elapsed = 60 * 60 * 24; // 1 day
|
|
}
|
|
|
|
return ($tokenTimeStored + $max_elapsed) >= time();
|
|
}
|
|
|
|
/**
|
|
* Destroy CSRF Token from Session
|
|
* @return bool success
|
|
*/
|
|
private static function destroyCsrfToken(string $csrfTokenKeyName = ""): bool
|
|
{
|
|
if (empty($csrfTokenKeyName)) {
|
|
$csrfTokenKeyName = $_POST['csrf_token'] ?? "";
|
|
}
|
|
if (!isset($_SESSION['csrf_pool'][$csrfTokenKeyName])) {
|
|
return false;
|
|
}
|
|
unset($_SESSION['csrf_pool'][$csrfTokenKeyName]); // Consume token
|
|
return true;
|
|
}
|
|
}
|
|
|